Compare commits

...

10 Commits

Author SHA1 Message Date
094ff95f71 . 2026-05-22 17:11:31 +09:00
e280f941e6 ごめん・・・ 2026-05-22 16:58:18 +09:00
b27063691f キャッシュとCURLライブラリーの修正 2026-05-22 16:56:39 +09:00
83e251cab3 ダッシュボードの確認済み 2026-05-11 02:31:54 +09:00
3a70309721 忘れた 2026-05-06 04:29:42 +09:00
65fa46e5a9 Merge pull request 'PHP 8.5が必須になった' (#33) from php85 into master
Reviewed-on: http://192.168.10.104:3000/suwako/LittleBeast/pulls/33
2026-05-06 03:51:03 +09:00
ca9e2f8b1d テスト済み 2026-05-06 04:11:03 +09:00
18fe4030a3 PHP 8.5が必須になった 2026-05-06 03:46:11 +09:00
0223951285 . 2026-05-04 14:44:31 +09:00
fc777da399 請求の確認 2026-05-04 14:15:29 +09:00
27 changed files with 597 additions and 486 deletions

1
.gitignore vendored
View File

@@ -8,3 +8,4 @@ public/static/user
!public/static/user/.kara
config/config.php
public/static/*.pem
.vscode

View File

@@ -1,5 +1,6 @@
# 0.5 (2026年??月??日)
* OpenProviderライブラリ
* PHP 8.5以上が必須になった
# ローリング・リリース (2025年01月01日)
* 最初プライベートリリース

View File

@@ -7,7 +7,7 @@
サンプル:[https://lbdemo.technicalsuwako.moe/](https://lbdemo.technicalsuwako.moe/)
## Little Beast とは?
Little Beast は PHP 8.3 以上向けのフレームワークで、076.moeゲーム開発会社と technicalsuwako.moe社長のブログ向けに作られました。\
Little Beast は PHP 8.5 以上向けのフレームワークで、076.moeゲーム開発会社と technicalsuwako.moe社長のブログ向けに作られました。\
メイン考え方は「必要な物だけ拡張し、不要な物は削除する」です。\
各コア機能はライブラリに分割されている為、必要な物だけを選び易い設計になっています。\
全てのモジュールはゼロから書かれており、データベースは一切必要ありません。
@@ -68,9 +68,9 @@ HTTP サーバーを設定して `/public` をルートとして `php-fpm` で
### OpenBSD サーバー
```sh
pkg_add php-8.4.14 php-gmp-8.4.14
rcctl enable php84_fpm httpd relayd
rcctl start php84_fpm httpd relayd
pkg_add php-8.5.5 php-gmp-8.5.5
rcctl enable php85_fpm httpd relayd
rcctl start php85_fpm httpd relayd
```
#### httpd

View File

@@ -7,7 +7,7 @@ Simple, Pragmatic, Anti-bloat
Demo installation: [https://lbdemo.technicalsuwako.moe/](https://lbdemo.technicalsuwako.moe/)
## What is Little Beast?
Little Beast is a PHP 8.3 or above framework made for 076.moe (game developer company) and technicalsuwako.moe (CEO's blog).\
Little Beast is a PHP 8.5 or above framework made for 076.moe (game developer company) and technicalsuwako.moe (CEO's blog).\
The core mentality is to extend what you need, and remove what you don't need.\
Each core feature is split into libraries, so it is easy to choose what you need.\
All modules are written from scratch, nothing is to be require a database.
@@ -68,9 +68,9 @@ That's all!
### OpenBSD server
```sh
pkg_add php-8.4.14 php-gmp-8.4.14
rcctl enable php84_fpm httpd relayd
rcctl start php84_fpm httpd relayd
pkg_add php-8.5.5 php-gmp-8.5.5
rcctl enable php85_fpm httpd relayd
rcctl start php85_fpm httpd relayd
```
#### httpd

View File

@@ -71,6 +71,19 @@ a.op-button-edit:hover {
color: #fcfcfc;
}
div.op-grid {
display: grid;
grid-auto-flow: column;
margin: 8px 0;
}
div.op-grid-item {
margin: 4px;
padding: 2px;
border: 1px solid #fcfcfc;
border-radius: 2px;
}
tr.status-free { background-color: #34860e; color: #fff; }
tr.status-reserved { background-color: #c59e1d; color: #fff; }
tr.status-use { background-color: #850000; color: #fff; }

View File

@@ -66,7 +66,7 @@ $routes = [
];
if (ACTIVITYPUB_ENABLED) {
$routes[] = Route::add('GET', '.well-known/webfinger', Home::class.'@apfinger');
$routes[] = Route::add('GET', '.well-known/webfinger', Fediverse::class.'@apfinger');
$routes[] = Route::add('GET', 'ap/following', Fediverse::class.'@apfollowing');
$routes[] = Route::add('GET', 'ap/followers', Fediverse::class.'@apfollowers');
$routes[] = Route::add('GET', 'ap/outbox', Fediverse::class.'@apoutbox');
@@ -96,6 +96,9 @@ if (ATOM_ENABLED) {
if (OPENPROVIDER_ENABLED) {
$routes[] = Route::add('GET', 'openprovider', Op::class.'@index');
$routes[] = Route::add('GET', 'openprovider/listinvoices', Op::class.'@opListInvoices');
$routes[] = Route::add('GET', 'openprovider/listpayments', Op::class.'@opListPayments');
$routes[] = Route::add('GET', 'openprovider/listtransactions', Op::class.'@opListTransactions');
$routes[] = Route::add('GET', 'openprovider/listcustomers', Op::class.'@opListCustomers');
$routes[] = Route::add('GET', 'openprovider/createcustomer', Op::class.'@opCreateCustomer');
$routes[] = Route::add('GET', 'openprovider/deletecustomer/{handle}', Op::class.'@opDeleteCustomer');

View File

@@ -37,6 +37,8 @@ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace Site\Controller;
use Roles;
class BlogPost {
/**
* ブログ投稿を取得する
@@ -46,9 +48,9 @@ class BlogPost {
public function getPosts(string $section, ?\stdClass $user = null): array {
$path = ROOT.$section;
$posts = [];
$isMember = $user !== NULL && $user->role !== \Roles::BANNED;
$isPaywall = $user !== NULL && $user->role >= \Roles::SUBSCRIBER;
$isStaff = $user !== NULL && $user->role & (\Roles::ADMIN | \Roles::STAFF);
$isMember = $user !== NULL && $user->role !== Roles::BANNED;
$isPaywall = $user !== NULL && $user->role >= Roles::SUBSCRIBER;
$isStaff = $user !== NULL && $user->role & (Roles::ADMIN | Roles::STAFF);
if (!is_dir($path)) return $posts;
$files = glob($path.'/*.md');

View File

@@ -40,6 +40,7 @@ namespace Site\Controller;
use Site\Controller\BlogPost;
use Site\Controller\Mods;
use Std\Lib\Activitypub;
use LogType;
class Fediverse extends BlogPost {
use Mods;
@@ -98,7 +99,7 @@ class Fediverse extends BlogPost {
exit;
}
logger(\LogType::ActivityPub, "受付に入れた:".json_encode($activity));
logger(LogType::ActivityPub, "受付に入れた:".json_encode($activity));
try {
header('Content-Type: application/activity+json');

View File

@@ -43,6 +43,7 @@ use Std\Lib\Activitypub;
use Std\Lib\Auth;
use Std\Lib\Markdown;
use Std\Lib\Template;
use Roles;
class Home extends BlogPost {
use Mods;
@@ -149,9 +150,9 @@ class Home extends BlogPost {
// ユーザー
$auth = new Auth();
$user = $auth->getLoggedInUser();
$isMember = $user !== NULL && $user->role !== \Roles::BANNED;
$isPaywall = $user !== NULL && $user->role >= \Roles::SUBSCRIBER;
$isStaff = $user !== NULL && $user->role & (\Roles::ADMIN | \Roles::STAFF);
$isMember = $user !== NULL && $user->role !== Roles::BANNED;
$isPaywall = $user !== NULL && $user->role >= Roles::SUBSCRIBER;
$isStaff = $user !== NULL && $user->role & (Roles::ADMIN | Roles::STAFF);
$tmpl->assign('user', $user);
$tmpl->assign('pagetit', $pagetit);

View File

@@ -39,7 +39,7 @@ namespace Site\Controller;
trait Mods {
public function getMenu(): array {
return [
$menu = [
[
'class' => 'menu-item',
'href' => '/',
@@ -82,13 +82,18 @@ trait Mods {
'text' => 'スタッフ限定',
'show' => true,
],
[
];
if (OPENPROVIDER_ENABLED) {
$menu[] = [
'class' => 'menu-item',
'href' => '/openprovider',
'page' => 'openprovider',
'text' => 'OpenProvider管理',
'show' => true,
],
];
];
}
return $menu;
}
}

View File

@@ -46,30 +46,100 @@ use Roles;
class Op {
use Mods;
private Template $tmpl;
private Auth $auth;
private Openprovider $op;
private string $pagetit = 'OpenProvider管理 - ';
private string $description = '';
private ?\stdClass $user;
public function __construct() {
$this->tmpl = new Template('/openprovider/');
$this->auth = new Auth();
$this->user = $this->auth->getLoggedInUser();
if ($this->user && $this->user->role & (Roles::ADMIN)) {
$this->op = new Openprovider();
$this->op->login();
}
$this->tmpl->assign('user', $this->user);
$this->tmpl->assign('curPage', 'openprovider');
$this->tmpl->assign('custCss', false);
$this->tmpl->assign('menu', $this->getMenu());
$this->tmpl->assign('description', $this->description);
$this->tmpl->addCss('openprovider');
$this->tmpl->addCss('table');
}
public function index(array $params): void {
try {
$tmpl = new Template('/openprovider/');
$pagetit = 'OpenProvider管理 - ホーム';
$description = '';
$this->pagetit .= 'ホーム';
$this->tmpl->assign('pagetit', $this->pagetit);
// ユーザー
$auth = new Auth();
$user = $auth->getLoggedInUser();
$tmpl->assign('user', $user);
$tmpl->assign('pagetit', $pagetit);
$tmpl->assign('curPage', 'openprovider');
$tmpl->assign('custCss', false);
$tmpl->assign('menu', $this->getMenu());
$tmpl->assign('description', $description);
if (!$user || $user->role !== Roles::ADMIN) goto noaccess;
$tmpl->addCss('table');
$tmpl->render('index');
if (!$this->user || $this->user->role !== Roles::ADMIN) goto noaccess;
$data = $this->op->getReseller(['with_statistics' => true]);
$domain = $this->op->listDomains(['order_by.renewal_date' => 'asc', 'status' => 'ACT']);
$this->tmpl->assign('data', $data->data);
$this->tmpl->assign('domain', $domain->data);
$this->tmpl->render('index');
exit();
noaccess:
$tmpl->render('nopermission');
$this->tmpl->render('nopermission');
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
}
public function opListInvoices(array $params): void {
try {
$this->pagetit .= '請求書一覧';
$this->tmpl->assign('pagetit', $this->pagetit);
if (!$this->user || $this->user->role !== Roles::ADMIN) goto noaccess;
$data = $this->op->listInvoices();
$this->tmpl->assign('data', $data->data);
$this->tmpl->render('listinvoices');
exit();
noaccess:
$this->tmpl->render('nopermission');
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
}
public function opListPayments(array $params): void {
try {
$this->pagetit .= '請求書一覧';
$this->tmpl->assign('pagetit', $this->pagetit);
if (!$this->user || $this->user->role !== Roles::ADMIN) goto noaccess;
$data = $this->op->listPayments();
$this->tmpl->assign('data', $data->data);
$this->tmpl->render('listpayments');
exit();
noaccess:
$this->tmpl->render('nopermission');
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
}
public function opListTransactions(array $params): void {
try {
$this->pagetit .= '請求書一覧';
$this->tmpl->assign('pagetit', $this->pagetit);
if (!$this->user || $this->user->role !== Roles::ADMIN) goto noaccess;
$data = $this->op->listTransactions();
$this->tmpl->assign('data', $data->data);
$this->tmpl->render('listtransactions');
exit();
noaccess:
$this->tmpl->render('nopermission');
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
@@ -77,34 +147,17 @@ class Op {
public function opListCustomers(array $params): void {
try {
$tmpl = new Template('/openprovider/');
$pagetit = 'OpenProvider管理 - 顧客様検索';
$description = '';
$this->pagetit .= '顧客様検索';
$this->tmpl->assign('pagetit', $this->pagetit);
// ユーザー
$auth = new Auth();
$user = $auth->getLoggedInUser();
$tmpl->assign('user', $user);
$tmpl->assign('pagetit', $pagetit);
$tmpl->assign('curPage', 'openprovider');
$tmpl->assign('custCss', true);
$tmpl->assign('menu', $this->getMenu());
$tmpl->assign('description', $description);
if (!$user || $user->role !== Roles::ADMIN) goto noaccess;
$op = new Openprovider();
$op->login();
$data = $op->listCustomers();
$tmpl->assign('data', $data->data);
$tmpl->addCss('table');
$tmpl->addCss('openprovider');
$tmpl->render('listcustomers');
if (!$this->user || $this->user->role !== Roles::ADMIN) goto noaccess;
$data = $this->op->listCustomers();
$this->tmpl->assign('data', $data->data);
$this->tmpl->render('listcustomers');
exit();
noaccess:
$tmpl->render('nopermission');
$this->tmpl->render('nopermission');
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
@@ -159,24 +212,12 @@ class Op {
'subscriber_number' => $_POST['phone_subscriber_number'] ?? '',
],
];
$tmpl = new Template('/openprovider/');
$pagetit = "OpenProvider管理 - 顧客様の新規作成";
$description = '';
// ユーザー
$auth = new Auth();
$user = $auth->getLoggedInUser();
$tmpl->assign('user', $user);
$tmpl->assign('pagetit', $pagetit);
$tmpl->assign('curPage', 'openprovider');
$tmpl->assign('custCss', true);
$tmpl->assign('menu', $this->getMenu());
$tmpl->assign('description', $description);
$this->pagetit .= '顧客様の新規作成';
$this->tmpl->assign('pagetit', $this->pagetit);
$err = [];
if (!$user || $user->role !== Roles::ADMIN) goto noaccess;
if (!$this->user || $this->user->role !== Roles::ADMIN) goto noaccess;
if (!empty($_POST)) {
if (NULL === $_POST['last_name'] || $_POST['last_name'] === ''
@@ -199,23 +240,20 @@ class Op {
if (($_POST['fax_area_code'] !== '' || $_POST['fax_subscriber_number'] !== '') && (!filter_var($_POST['fax_area_code'], FILTER_VALIDATE_INT) || !filter_var($_POST['fax_subscriber_number'], FILTER_VALIDATE_INT))) $err[] = 'FAXは数字をご入力下さい。';
if (!empty($err)) goto render;
$op = new Openprovider();
$op->login();
$data = $op->createCustomer($payload, true);
$data = $this->op->createCustomer($payload, true);
header("Location: /openprovider/getcustomer/{$data->data['data']['handle']}");
exit();
}
render:
$tmpl->assign('err', $err);
$tmpl->assign('data', $payload);
$tmpl->addCss('openprovider');
$tmpl->render('createcustomer');
$this->tmpl->assign('err', $err);
$this->tmpl->assign('data', $payload);
$this->tmpl->render('createcustomer');
exit();
noaccess:
$tmpl->render('nopermission');
$this->tmpl->render('nopermission');
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
@@ -229,34 +267,17 @@ class Op {
header('Location: /openprovider/listcustomers');
exit();
}
$tmpl = new Template('/openprovider/');
$pagetit = "OpenProvider管理 - 顧客様「{$handle}」の表示";
$description = '';
$this->pagetit .= "顧客様「{$handle}」の表示";
$this->tmpl->assign('pagetit', $this->pagetit);
// ユーザー
$auth = new Auth();
$user = $auth->getLoggedInUser();
$tmpl->assign('user', $user);
$tmpl->assign('pagetit', $pagetit);
$tmpl->assign('curPage', 'openprovider');
$tmpl->assign('custCss', true);
$tmpl->assign('menu', $this->getMenu());
$tmpl->assign('description', $description);
if (!$user || $user->role !== Roles::ADMIN) goto noaccess;
$op = new Openprovider();
$op->login();
$data = $op->getCustomer($handle, true);
$tmpl->assign('data', $data->data);
$tmpl->addCss('table');
$tmpl->addCss('openprovider');
$tmpl->render('getcustomer');
if (!$this->user || $this->user->role !== Roles::ADMIN) goto noaccess;
$data = $this->op->getCustomer($handle, true);
$this->tmpl->assign('data', $data->data);
$this->tmpl->render('getcustomer');
exit();
noaccess:
$tmpl->render('nopermission');
$this->tmpl->render('nopermission');
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
@@ -271,44 +292,29 @@ class Op {
exit();
}
assert_not_null($handle);
$tmpl = new Template('/openprovider/');
$pagetit = "OpenProvider管理 - 顧客様「{$handle}」の表示";
$description = '';
$this->pagetit .= "顧客様「{$handle}」の表示";
$this->tmpl->assign('pagetit', $this->pagetit);
// ユーザー
$auth = new Auth();
$user = $auth->getLoggedInUser();
$tmpl->assign('user', $user);
$tmpl->assign('pagetit', $pagetit);
$tmpl->assign('curPage', 'openprovider');
$tmpl->assign('custCss', true);
$tmpl->assign('menu', $this->getMenu());
$tmpl->assign('description', $description);
if (!$user || $user->role !== Roles::ADMIN) goto noaccess;
if (!$this->user || $this->user->role !== Roles::ADMIN) goto noaccess;
if (empty($_POST)) goto render_confirm;
if (isset($_POST['delete_reject'])) {
header("Location: /openprovider/getcustomer/{$handle}");
exit();
} else if (isset($_POST['delete_confirm'])) {
$op = new Openprovider();
$op->login();
$op->deleteCustomer($handle);
$this->op->deleteCustomer($handle);
}
header('Location: /openprovider/listcustomers');
exit();
render_confirm:
$tmpl->assign('handle', $handle);
$tmpl->addCss('openprovider');
$tmpl->render('deletecustomer');
$this->tmpl->assign('handle', $handle);
$this->tmpl->render('deletecustomer');
exit();
noaccess:
$tmpl->render('nopermission');
$this->tmpl->render('nopermission');
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
@@ -316,33 +322,17 @@ class Op {
public function opListTlds(array $params): void {
try {
$tmpl = new Template('/openprovider/');
$pagetit = 'OpenProvider管理 - TLD一覧';
$description = '';
$this->pagetit .= 'TLD一覧';
$this->tmpl->assign('pagetit', $this->pagetit);
// ユーザー
$auth = new Auth();
$user = $auth->getLoggedInUser();
$tmpl->assign('user', $user);
$tmpl->assign('pagetit', $pagetit);
$tmpl->assign('curPage', 'openprovider');
$tmpl->assign('custCss', false);
$tmpl->assign('menu', $this->getMenu());
$tmpl->assign('description', $description);
if (!$user || $user->role !== Roles::ADMIN) goto noaccess;
$op = new Openprovider();
$op->login();
$data = $op->listTlds();
$tmpl->assign('data', $data->data);
$tmpl->addCss('table');
$tmpl->render('listtlds');
if (!$this->user || $this->user->role !== Roles::ADMIN) goto noaccess;
$data = $this->op->listTlds();
$this->tmpl->assign('data', $data->data);
$this->tmpl->render('listtlds');
exit();
noaccess:
$tmpl->render('nopermission');
$this->tmpl->render('nopermission');
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
@@ -356,33 +346,17 @@ class Op {
header('Location: /');
exit();
}
$tmpl = new Template('/openprovider/');
$pagetit = "OpenProvider管理 - .{$tld}の表示";
$description = '';
$this->pagetit .= "{$tld}の表示";
$this->tmpl->assign('pagetit', $this->pagetit);
// ユーザー
$auth = new Auth();
$user = $auth->getLoggedInUser();
$tmpl->assign('user', $user);
$tmpl->assign('pagetit', $pagetit);
$tmpl->assign('curPage', 'openprovider');
$tmpl->assign('custCss', false);
$tmpl->assign('menu', $this->getMenu());
$tmpl->assign('description', $description);
if (!$user || $user->role !== Roles::ADMIN) goto noaccess;
$op = new Openprovider();
$op->login();
$data = $op->getTld($tld);
$tmpl->assign('data', $data->data);
$tmpl->addCss('table');
$tmpl->render('gettld');
if (!$this->user || $this->user->role !== Roles::ADMIN) goto noaccess;
$data = $this->op->getTld($tld);
$this->tmpl->assign('data', $data->data);
$this->tmpl->render('gettld');
exit();
noaccess:
$tmpl->render('nopermission');
$this->tmpl->render('nopermission');
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
@@ -390,36 +364,17 @@ class Op {
public function opListDomains(array $params): void {
try {
$tmpl = new Template('/openprovider/');
$pagetit = 'OpenProvider管理 - ドメイン確認';
$description = '';
$this->pagetit .= 'ドメイン確認';
$this->tmpl->assign('pagetit', $this->pagetit);
// ユーザー
$auth = new Auth();
$user = $auth->getLoggedInUser();
$tmpl->assign('user', $user);
$tmpl->assign('pagetit', $pagetit);
$tmpl->assign('curPage', 'openprovider');
$tmpl->assign('custCss', false);
$tmpl->assign('menu', $this->getMenu());
$tmpl->assign('description', $description);
if (!$user || $user->role !== Roles::ADMIN) goto noaccess;
$op = new Openprovider();
$op->login();
$data = $op->listDomains($domains, true);
$tmpl->assign('data', $data->data);
$tmpl->assign('saved', $saved);
$tmpl->addCss('table');
$tmpl->addCss('openprovider');
$tmpl->render('checkdomain');
if (!$this->user || $this->user->role !== Roles::ADMIN) goto noaccess;
$data = $this->op->listDomains($domains, true);
$this->tmpl->assign('data', $data->data);
$this->tmpl->render('checkdomain');
exit();
noaccess:
$tmpl->render('nopermission');
$this->tmpl->render('nopermission');
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
@@ -429,40 +384,22 @@ class Op {
try {
$domains = isset($_GET['domains']) ? explode("\n", $_GET['domains']) : [];
$saved = '';
$this->pagetit .= 'ドメイン確認';
$this->tmpl->assign('pagetit', $this->pagetit);
$tmpl = new Template('/openprovider/');
$pagetit = 'OpenProvider管理 - ドメイン確認';
$description = '';
// ユーザー
$auth = new Auth();
$user = $auth->getLoggedInUser();
$tmpl->assign('user', $user);
$tmpl->assign('pagetit', $pagetit);
$tmpl->assign('curPage', 'openprovider');
$tmpl->assign('custCss', false);
$tmpl->assign('menu', $this->getMenu());
$tmpl->assign('description', $description);
if (!$user || $user->role !== Roles::ADMIN) goto noaccess;
$op = new Openprovider();
$op->login();
if (!$this->user || $this->user->role !== Roles::ADMIN) goto noaccess;
if (!empty($domains)) {
$data = $op->checkDomainAvailable($domains, true);
$tmpl->assign('data', $data->data);
$data = $this->op->checkDomainAvailable($domains, true);
$this->tmpl->assign('data', $data->data);
$saved = $_GET['domains'];
}
$tmpl->assign('saved', $saved);
$tmpl->addCss('table');
$tmpl->addCss('openprovider');
$tmpl->render('checkdomain');
$this->tmpl->assign('saved', $saved);
$this->tmpl->render('checkdomain');
exit();
noaccess:
$tmpl->render('nopermission');
$this->tmpl->render('nopermission');
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
@@ -472,34 +409,17 @@ class Op {
try {
$domainname = $_GET['domain_name'] ?? '';
$domainext = $_GET['domain_extension'] ?? '';
$this->pagetit .= 'ドメイン値段一覧';
$this->tmpl->assign('pagetit', $this->pagetit);
$tmpl = new Template('/openprovider/');
$pagetit = 'OpenProvider管理 - ドメイン値段一覧';
$description = '';
// ユーザー
$auth = new Auth();
$user = $auth->getLoggedInUser();
$tmpl->assign('user', $user);
$tmpl->assign('pagetit', $pagetit);
$tmpl->assign('curPage', 'openprovider');
$tmpl->assign('custCss', false);
$tmpl->assign('menu', $this->getMenu());
$tmpl->assign('description', $description);
if (!$user || $user->role !== Roles::ADMIN) goto noaccess;
$op = new Openprovider();
$op->login();
$data = $op->getDomainPrices(['domain.name' => $domainname, 'domain.extension' => $domainext]);
$tmpl->assign('data', $data->data);
$tmpl->addCss('table');
$tmpl->render('getdomainprices');
if (!$this->user || $this->user->role !== Roles::ADMIN) goto noaccess;
$data = $this->op->getDomainPrices(['domain.name' => $domainname, 'domain.extension' => $domainext]);
$this->tmpl->assign('data', $data->data);
$this->tmpl->render('getdomainprices');
exit();
noaccess:
$tmpl->render('nopermission');
$this->tmpl->render('nopermission');
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
@@ -507,34 +427,17 @@ class Op {
public function opListDns(array $params): void {
try {
$tmpl = new Template('/openprovider/');
$pagetit = "OpenProvider管理 - DNS一覧";
$description = '';
$this->pagetit .= 'DNS一覧';
$this->tmpl->assign('pagetit', $this->pagetit);
// ユーザー
$auth = new Auth();
$user = $auth->getLoggedInUser();
$tmpl->assign('user', $user);
$tmpl->assign('pagetit', $pagetit);
$tmpl->assign('curPage', 'openprovider');
$tmpl->assign('custCss', true);
$tmpl->assign('menu', $this->getMenu());
$tmpl->assign('description', $description);
if (!$user || $user->role !== Roles::ADMIN) goto noaccess;
$op = new Openprovider();
$op->login();
$data = $op->listDnsZones();
$tmpl->assign('data', $data->data);
$tmpl->addCss('table');
if ($user && $user->role & (Roles::ADMIN)) $tmpl->render('listdnszones');
else goto noaccess;
if (!$this->user || $this->user->role !== Roles::ADMIN) goto noaccess;
$data = $this->op->listDnsZones();
$this->tmpl->assign('data', $data->data);
$this->tmpl->render('listdnszones');
exit();
noaccess:
$tmpl->render('nopermission');
$this->tmpl->render('nopermission');
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
@@ -548,33 +451,17 @@ class Op {
header('Location: /');
exit();
}
$tmpl = new Template('/openprovider/');
$pagetit = "OpenProvider管理 - {$domain}のDNS管理";
$description = '';
$this->pagetit .= "{$domain}のDNS管理";
$this->tmpl->assign('pagetit', $this->pagetit);
// ユーザー
$auth = new Auth();
$user = $auth->getLoggedInUser();
$tmpl->assign('user', $user);
$tmpl->assign('pagetit', $pagetit);
$tmpl->assign('curPage', 'openprovider');
$tmpl->assign('custCss', false);
$tmpl->assign('menu', $this->getMenu());
$tmpl->assign('description', $description);
if (!$user || $user->role !== Roles::ADMIN) goto noaccess;
$op = new Openprovider();
$op->login();
$data = ['rec' => $op->listZoneRecords($domain), 'zone' => $op->getDnsZone($domain)];
$tmpl->assign('data', $data->data);
$tmpl->addCss('table');
$tmpl->render('getdns');
if (!$this->user || $this->user->role !== Roles::ADMIN) goto noaccess;
$data = ['rec' => $this->op->listZoneRecords($domain), 'zone' => $this->op->getDnsZone($domain)];
$this->tmpl->assign('data', $data->data);
$this->tmpl->render('getdns');
exit();
noaccess:
$tmpl->render('nopermission');
$this->tmpl->render('nopermission');
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}

View File

@@ -40,6 +40,7 @@ namespace Site\Controller;
use Site\Controller\Mods;
use Std\Lib\Auth;
use Std\Lib\Template;
use Roles;
class Page {
use Mods;
@@ -132,7 +133,7 @@ class Page {
$tmpl->assign('menu', $this->getMenu());
$tmpl->assign('description', $description);
if ($user && $user->role !== \Roles::BANNED) $tmpl->render('memberonly');
if ($user && $user->role !== Roles::BANNED) $tmpl->render('memberonly');
else $tmpl->render('nopermission');
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
@@ -156,7 +157,7 @@ class Page {
$tmpl->assign('menu', $this->getMenu());
$tmpl->assign('description', $description);
if ($user && $user->role & (\Roles::ADMIN | \Roles::STAFF)) $tmpl->render('staffonly');
if ($user && $user->role & (Roles::ADMIN | Roles::STAFF)) $tmpl->render('staffonly');
else $tmpl->render('nopermission');
} catch (\Exception $e) {
throw new \Exception($e->getMessage());

View File

@@ -38,6 +38,8 @@ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
namespace Std\Lib;
use Std\Lib\Curl;
use LogType;
use Uri\Rfc3986\Uri;
/**
* ActivityPubプロトコルの実装クラス
@@ -297,14 +299,14 @@ class ActivityPub {
*/
public function getWebfinger(): string {
if (!ACTIVITYPUB_ENABLED) return '';
$domain = str_replace(['http://', 'https://'], '', $this->domain);
$uri = new Uri($this->domain);
$webfinger = [
'subject' => "acct:{$this->actor}@{$domain}",
'subject' => "acct:{$this->actor}@{$uri->getHost()}",
'links' => [
[
'rel' => 'self',
'type' => 'application/activity+json',
'href' => "{$domain}/ap/actor",
'href' => "{$this->domain}/ap/actor",
],
],
];
@@ -434,7 +436,7 @@ class ActivityPub {
$privFile = FEDIINFO['privkey'];
$priv = file_get_contents($privFile);
if ($priv === false) {
logger(\LogType::ActivityPub, "エラー:秘密鍵「{$privFile}」の読込に失敗");
logger(LogType::ActivityPub, "エラー:秘密鍵「{$privFile}」の読込に失敗");
header('HTTP/1.1 500 Internal Server Error');
header('Content-Type: application/activity+json');
echo json_encode(['error' => '秘密鍵の読込に失敗']);
@@ -444,21 +446,21 @@ class ActivityPub {
$body = json_encode($activity, JSON_UNESCAPED_SLASHES);
$digest = base64_encode(hash('sha256', $body, true));
$date = gmdate('D, d M Y H:i:s \G\M\T');
$host = parse_url($inboxUrl, PHP_URL_HOST);
$uri = new Uri($inboxUrl);
$headers = [
'Host' => $host,
'Host' => $uri->getHost().($uri->getPort() ? ':'.$uri->getPort() : ''),
'Date' => $date,
'Content-Type' => 'application/activity+json',
'Digest' => "SHA-256=$digest",
];
$stringToSign = "host: {$headers['Host']}\n"."date: {$headers['Date']}\n"."digest: {$headers['Digest']}";
logger(\LogType::ActivityPub, "署名対象: {$stringToSign}");
logger(LogType::ActivityPub, "署名対象: {$stringToSign}");
if (!openssl_sign($stringToSign, $signature, $priv, OPENSSL_ALGO_SHA256)) {
$error = openssl_error_string();
logger(\LogType::ActivityPub, "エラー:署名に失敗: {$error}");
logger(LogType::ActivityPub, "エラー:署名に失敗: {$error}");
header('HTTP/1.1 500 Internal Server Error');
header('Content-Type: application/activity+json');
echo json_encode(['error' => '署名に失敗']);
@@ -470,7 +472,7 @@ class ActivityPub {
$headers['Signature'] .= 'algorithm="rsa-sha256",';
$headers['Signature'] .= 'headers="host date digest",';
$headers['Signature'] .= 'signature="'.$sigValue.'"';
logger(\LogType::ActivityPub, "署名: {$headers['Signature']}\n送信データ: {$body}");
logger(LogType::ActivityPub, "署名: {$headers['Signature']}\n送信データ: {$body}");
$curl = new Curl($inboxUrl);
$curl->setMethod('POST')
@@ -486,9 +488,9 @@ class ActivityPub {
$err = $curl->getError();
var_dump(print_r($res));
logger(\LogType::ActivityPub, "アクティビティは「{$inboxUrl}」に送信しました: HTTP {$code}");
logger(\LogType::ActivityPub, "エラー: {$err}");
logger(\LogType::ActivityPub, "レスポンス: {$res}");
logger(LogType::ActivityPub, "アクティビティは「{$inboxUrl}」に送信しました: HTTP {$code}");
logger(LogType::ActivityPub, "エラー: {$err}");
logger(LogType::ActivityPub, "レスポンス: {$res}");
}
/**
@@ -571,10 +573,10 @@ class ActivityPub {
->setMaxRedirects(5)
->setCaInfo('/etc/ssl/cert.pem');
logger(\LogType::ActivityPub, "アクターURLにリクエスト: {$actor}");
logger(LogType::ActivityPub, "アクターURLにリクエスト: {$actor}");
$success = $curl->execute();
if (!$success) {
logger(\LogType::ActivityPub, "アクターリクエストに失敗: ".$curl->getError());
logger(LogType::ActivityPub, "アクターリクエストに失敗: ".$curl->getError());
return null;
}
@@ -583,7 +585,7 @@ class ActivityPub {
$err = $curl->getError();
if ($code !== 200) {
logger(\LogType::ActivityPub, "アクター取得に失敗: HTTP {$code}, エラー: {$err}");
logger(LogType::ActivityPub, "アクター取得に失敗: HTTP {$code}, エラー: {$err}");
return null;
}

View File

@@ -37,6 +37,11 @@ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace Std\Lib;
use Gender;
use LogType;
use Result;
use Roles;
class Auth {
private int $id;
@@ -72,22 +77,22 @@ class Auth {
unset($user->password);
unset($user->tokens);
$myself = $this->getUserData();
if (!$myself || ($myself->id != $user->id && $myself->role < \Roles::STAFF)) $user->email = '(秘密)';
if (!$myself || ($myself->id != $user->id && $myself->role < Roles::STAFF)) $user->email = '(秘密)';
$user->name = namecolor($user);
$user->regDate = date('Y年m月d日', $user->regDate);
$user->gender = $user->gender === \Gender::MALE ? '男' : ($user->gender === \Gender::FEMALE ? '女' : '不明');
$user->gender = $user->gender === Gender::MALE ? '男' : ($user->gender === Gender::FEMALE ? '女' : '不明');
switch (true) {
case ($user->role & \Roles::ADMIN):
case ($user->role & Roles::ADMIN):
$user->role = '管理者';
break;
case ($user->role & \Roles::STAFF):
case ($user->role & Roles::STAFF):
$user->role = '076スタジオの会社員';
break;
case ($user->role & (\Roles::FSUBSCRIBER | \Roles::PSUBSCRIBER | \Roles::NSUBSCRIBER | \Roles::SUBSCRIBER)):
case ($user->role & (Roles::FSUBSCRIBER | Roles::PSUBSCRIBER | Roles::NSUBSCRIBER | Roles::SUBSCRIBER)):
$user->role = '支援者♡';
break;
case ($user->role & \Roles::BANNED):
case ($user->role & Roles::BANNED):
$user->role = 'BANされた';
break;
default:
@@ -103,14 +108,14 @@ class Auth {
return $user;
}
public function setToken(string $username, #[SensitiveParameter] string $password): \Result {
if (!AUTH_ENABLED) return \Result::Error('エラー:認証システムは無効です。');
public function setToken(string $username, #[SensitiveParameter] string $password): Result {
if (!AUTH_ENABLED) return Result::Error('エラー:認証システムは無効です。');
$userData = $this->getUserData();
if (!isset($userData->tokens) || !is_array($userData->tokens)) $userData->tokens = [];
$verify = $this->verifyLogin($username, $password);
if (!$verify->isSuccess) {
return \Result::error($verify->message);
return Result::error($verify->message);
}
$ip = $_SERVER['REMOTE_ADDR'];
@@ -119,7 +124,9 @@ class Auth {
$ip = trim($ipList[0]);
}
$userData = $this->purgeOldTokens($userData);
$res = $this->purgeOldTokens($userData);
if (!$res->isSuccess) return Result::Error($res->message);
$userData = $res->data;
$token = bin2hex(random_bytes(64));
$expire = time() + $this->tokenDuration;
$newToken = [
@@ -137,8 +144,8 @@ class Auth {
$json = json_encode($userData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if (file_put_contents($path, $json) === false) {
logger(\LogType::Auth, '【setToken】JSONファイルにユーザーデータを保存出来なかった。パス'.$path);
return \Result::error('エラー:ユーザーデータの保存に失敗。');
logger(LogType::Auth, '【setToken】JSONファイルにユーザーデータを保存出来なかった。パス'.$path);
return Result::error('エラー:ユーザーデータの保存に失敗。');
}
if (!setcookie('kerozen', $token, [
@@ -149,22 +156,24 @@ class Auth {
'httponly' => true,
'samesite' => 'Strict'
])) {
logger(\LogType::Auth, '【setToken】クッキーを設定出来なかった。トークン'.$token);
return \Result::error('エラー:クッキーを設定に失敗。');
logger(LogType::Auth, '【setToken】クッキーを設定出来なかった。トークン'.$token);
return Result::error('エラー:クッキーを設定に失敗。');
}
return \Result::success('ログイン成功');
return Result::success('ログイン成功');
}
public function getToken(): string {
public function getToken(): ?string {
if (!AUTH_ENABLED) return '';
$userData = $this->getUserData();
if (!isset($userData->tokens) || !is_array($userData->tokens)) $userData->tokens = [];
$userData = $this->purgeOldTokens($userData);
$res = $this->purgeOldTokens($userData);
if (!$res->isSuccess) return null;
$userData = $res->data;
}
public function logout(?string $token = null): \Result {
if (!AUTH_ENABLED) return \Result::Error('エラー:認証システムは無効です。');
public function logout(?string $token = null): Result {
if (!AUTH_ENABLED) return Result::Error('エラー:認証システムは無効です。');
$userData = $this->getUserData();
if (!$token) {
@@ -182,27 +191,27 @@ class Auth {
'samesite' => 'Strict'
]);
return \Result::success();
return Result::success();
}
public function isUserExist(?string $username = null): \Result {
if (!AUTH_ENABLED) return \Result::Error('エラー:認証システムは無効です。');
if (null === $username) return \Result::Error('エラー:ユーザー名をご入力下さい。');
public function isUserExist(?string $username = null): Result {
if (!AUTH_ENABLED) return Result::Error('エラー:認証システムは無効です。');
if (null === $username) return Result::Error('エラー:ユーザー名をご入力下さい。');
$userList = scandir($this->dataDir);
foreach ($userList as $list) {
if ($list === '.kara' || $list === '.' || $list === '..') continue;
$file = str_replace('.json', '', $list);
$user = explode('.', $file)[1];
if ($username === $user) return \Result::Success("ユーザー「{$username}」は既に存在します。");
if ($username === $user) return Result::Success("ユーザー「{$username}」は既に存在します。");
}
return \Result::Error('');
return Result::Error('');
}
public function mkUser(?string $username = null, #[SensitiveParameter] ?string $password = null, ?string $passwordVerify = null, ?string $email = null): \Result {
if (!AUTH_ENABLED) return \Result::Error('エラー:認証システムは無効です。');
if (!AUTH_REGISTER_ENABLED) return \Result::Error('ユーザー登録は無効です。');
public function mkUser(?string $username = null, #[SensitiveParameter] ?string $password = null, ?string $passwordVerify = null, ?string $email = null): Result {
if (!AUTH_ENABLED) return Result::Error('エラー:認証システムは無効です。');
if (!AUTH_REGISTER_ENABLED) return Result::Error('ユーザー登録は無効です。');
$resUsr = $this->verifyUsername($username);
$resPwd = $this->verifyPassword($password, $passwordVerify);
$resEml = $this->verifyEmail($email);
@@ -213,12 +222,12 @@ class Auth {
if (!$resPwd->isSuccess) $err .= $resPwd->message."<br />";
if (!$resEml->isSuccess) $err .= $resEml->message."<br />";
if ($err != '') return \Result::Error($err);
if ($err != '') return Result::Error($err);
$err = '';
if (!mkdir($this->assDir.$username, 0755)) {
logger(\LogType::Auth, '【mkUser】アセットディレクトリを作成出来なかった。chownをご確認下さい。パス'.$this->assDir.$username);
return \Result::Error('エラー:ユーザーのアイコンディレクトリの作成に失敗。');
logger(LogType::Auth, '【mkUser】アセットディレクトリを作成出来なかった。chownをご確認下さい。パス'.$this->assDir.$username);
return Result::Error('エラー:ユーザーのアイコンディレクトリの作成に失敗。');
}
$file = scandir($this->dataDir);
@@ -246,29 +255,29 @@ class Auth {
$user->regDate = time();
$user->namecolor = '';
$user->displayname = '';
$user->gender = \Gender::UNKNOWN;
$user->role = \Roles::MEMBER;
$user->gender = Gender::UNKNOWN;
$user->role = Roles::MEMBER;
$user->tokens = [];
$path = "{$this->dataDir}{$lastId}.{$username}.json";
$json = json_encode($user, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if (file_put_contents($path, $json) === false) {
logger(\LogType::Auth, '【mkUser】JSONファイルを作成出来なかった。パス'.$path);
logger(LogType::Auth, '【mkUser】JSONファイルを作成出来なかった。パス'.$path);
rmdir($this->assDir.$username);
return \Result::Error('エラー:ユーザーデータの保存に失敗。');
return Result::Error('エラー:ユーザーデータの保存に失敗。');
}
return \Result::Success();
return Result::Success();
}
////////////////////
private function verifyLogin(string $username, #[SensitiveParameter] string $password): \Result {
if (!AUTH_ENABLED) return \Result::Error('エラー:認証システムは無効です。');
private function verifyLogin(string $username, #[SensitiveParameter] string $password): Result {
if (!AUTH_ENABLED) return Result::Error('エラー:認証システムは無効です。');
$userData = $this->getUserData();
if ($username !== $userData->username || !password_verify($password, $userData->password)) {
return \Result::Error('エラー:ユーザー名又はパスワードが一致していません。');
return Result::Error('エラー:ユーザー名又はパスワードが一致していません。');
}
if (password_needs_rehash($userData->password, $this->algo)) {
@@ -277,31 +286,31 @@ class Auth {
$json = json_encode($userData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if (file_put_contents($path, $json) === false) {
logger(\LogType::Auth, '【verifyLogin】JSONファイルを変更出来なかった。chownをご確認下さい。パス'.$path);
return \Result::Error('エラー:ユーザーデータの保存に失敗。');
logger(LogType::Auth, '【verifyLogin】JSONファイルを変更出来なかった。chownをご確認下さい。パス'.$path);
return Result::Error('エラー:ユーザーデータの保存に失敗。');
}
}
return \Result::Success();
return Result::Success();
}
private function purgeOldTokens(\stdClass $userData): \stdClass|\Result {
if (!AUTH_ENABLED) return \Result::Error('エラー:認証システムは無効です。');
private function purgeOldTokens(\stdClass $userData): Result {
if (!AUTH_ENABLED) return Result::Error('エラー:認証システムは無効です。');
// 有効期限切れたトークンの削除
$out = $userData;
$out->tokens = array_filter($userData->tokens, function($t): bool {
return ($t->expDate ?? 0) > time();
return Result::Success('', ($t->expDate ?? 0) > time());
});
$path = $this->dataDir.$userData->id.'.'.$userData->username.'.json';
$json = json_encode($userData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if (file_put_contents($path, $json) === false) {
logger(\LogType::Auth, '【purgeOldTokens】JSONファイルを変更出来なかった。chownをご確認下さい。パス'.$path);
return \Result::Error('エラー:ユーザーデータの保存に失敗。');
logger(LogType::Auth, '【purgeOldTokens】JSONファイルを変更出来なかった。chownをご確認下さい。パス'.$path);
return Result::Error('エラー:ユーザーデータの保存に失敗。');
}
return $out;
return Result::Success('', $out);
}
private function getUserId(?string $username = null, ?string $token = null): int {
@@ -396,8 +405,8 @@ class Auth {
return json_decode($lines);
}
private function isEmailExist(?string $email = null): \Result {
if (!AUTH_ENABLED) return \Result::Error('エラー:認証システムは無効です。');
private function isEmailExist(?string $email = null): Result {
if (!AUTH_ENABLED) return Result::Error('エラー:認証システムは無効です。');
$userList = scandir($this->dataDir);
$matches = [];
@@ -410,67 +419,67 @@ class Auth {
if (str_contains($content, '"email": "'.$email.'",')) {
$matches[] = $email;
}
if (count($matches) > 0) return \Result::Error("ユーザー「{$email}」は既に存在します。");
if (count($matches) > 0) return Result::Error("ユーザー「{$email}」は既に存在します。");
}
return \Result::Success('エラー:メールアドレスが存在しません。');
return Result::Success('エラー:メールアドレスが存在しません。');
}
private function verifyEmail(?string $email = null): \Result {
if (!AUTH_ENABLED) return \Result::Error('エラー:認証システムは無効です。');
if (null === $email || '' === $email) return \Result::Error('エラー:メールアドレスをご入力下さい。');
if (strpos($email, '@') === false) return \Result::Error('エラー:メールアドレスは不正です。');
private function verifyEmail(?string $email = null): Result {
if (!AUTH_ENABLED) return Result::Error('エラー:認証システムは無効です。');
if (null === $email || '' === $email) return Result::Error('エラー:メールアドレスをご入力下さい。');
if (strpos($email, '@') === false) return Result::Error('エラー:メールアドレスは不正です。');
$domain = explode('@', $email)[1];
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return \Result::Error('エラー:メールアドレスは不正です。');
return Result::Error('エラー:メールアドレスは不正です。');
}
if (!checkdnsrr($domain, 'MX')) {
return \Result::Error('エラー:メールアドレスは不正です。');
return Result::Error('エラー:メールアドレスは不正です。');
}
$res = $this->isEmailExist($email);
if (!$res->isSuccess) return \Result::Error($res->message);
if (!$res->isSuccess) return Result::Error($res->message);
return \Result::Success();
return Result::Success();
}
private function verifyUsername(?string $username): \Result {
if (!AUTH_ENABLED) return \Result::Error('エラー:認証システムは無効です。');
if (null === $username || '' === $username) return \Result::Error('エラー:ユーザー名をご入力下さい。');
if (strlen($username) < 6) return \Result::Error('エラーユーザー名は6文字以上をご入力下さい。');
private function verifyUsername(?string $username): Result {
if (!AUTH_ENABLED) return Result::Error('エラー:認証システムは無効です。');
if (null === $username || '' === $username) return Result::Error('エラー:ユーザー名をご入力下さい。');
if (strlen($username) < 6) return Result::Error('エラーユーザー名は6文字以上をご入力下さい。');
$res = $this->isUserExist($username);
if ($res->isSuccess) return \Result::Error($res->message);
if ($res->isSuccess) return Result::Error($res->message);
$accepted = 'A-Za-z0-9';
if (preg_match("/[^{$accepted}]/", $username)) {
return \Result::Error('エラー:ユーザー名に不正な文字が含みます。');
return Result::Error('エラー:ユーザー名に不正な文字が含みます。');
}
return \Result::Success();
return Result::Success();
}
private function verifyPassword(#[SensitiveParameter] ?string $password = null, #[SensitiveParameter] ?string $passwordVerify = null): \Result {
if (!AUTH_ENABLED) return \Result::Error('エラー:認証システムは無効です。');
if (null === $password || '' === $password) return \Result::Error('エラー:パスワードをご入力下さい。');
if ($password !== $passwordVerify) return \Result::Error('エラー:パスワードは一致していません。');
private function verifyPassword(#[SensitiveParameter] ?string $password = null, #[SensitiveParameter] ?string $passwordVerify = null): Result {
if (!AUTH_ENABLED) return Result::Error('エラー:認証システムは無効です。');
if (null === $password || '' === $password) return Result::Error('エラー:パスワードをご入力下さい。');
if ($password !== $passwordVerify) return Result::Error('エラー:パスワードは一致していません。');
$res = $this->checkPasswordStandards($password);
if (!$res->isSuccess) {
return \Result::Error($res->message);
return Result::Error($res->message);
}
return \Result::Success();
return Result::Success();
}
private function checkPasswordStandards(#[SensitiveParameter] ?string $password = null): \Result {
if (!AUTH_ENABLED) return \Result::Error('エラー:認証システムは無効です。');
if (strlen($password) < $this->minPassLen) return \Result::Error("エラー:パスワードは{$this->minPassLen}以上をご入力下さい。");
private function checkPasswordStandards(#[SensitiveParameter] ?string $password = null): Result {
if (!AUTH_ENABLED) return Result::Error('エラー:認証システムは無効です。');
if (strlen($password) < $this->minPassLen) return Result::Error("エラー:パスワードは{$this->minPassLen}以上をご入力下さい。");
if (!\countmatch($password)) {
return \Result::Error('エラー:パスワードに不正な文字が含みます。');
return Result::Error('エラー:パスワードに不正な文字が含みます。');
}
$countUpper = preg_match_all('/[A-Z]/', $password) < 2;
@@ -479,9 +488,9 @@ class Auth {
$countSymbol = \count_special_chars($password) < 2;
if ($countUpper || $countLower || $countDigit || $countSymbol) {
return \Result::Error('エラーパスワードは2つ以上の大文字、2つ以上の小文字、2つ以上の数字、及び2つ以上の特別文字を含む事が必須です。');
return Result::Error('エラーパスワードは2つ以上の大文字、2つ以上の小文字、2つ以上の数字、及び2つ以上の特別文字を含む事が必須です。');
}
return \Result::Success();
return Result::Success();
}
};

View File

@@ -47,7 +47,7 @@ class Cache {
protected int $dataDuration = 1800; // 30分
public function __construct(string $dataDir) {
$this->dataDir = ROOT.'/data/cache/openprovider/'.$dataDir.'/';
$this->dataDir = ROOT.'/data/cache/'.$dataDir.'/';
}
/**

View File

@@ -37,6 +37,9 @@ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace Std\Lib;
use Result;
use Uri\Rfc3986\Uri;
/**
* php_curlへの依存を排除するための独自のCURL実装
*/
@@ -317,11 +320,11 @@ class Curl {
*
* @return Result 成功または失敗
*/
public function execute(): \Result {
if (!CURL_ENABLED) return \Result::Error('エラー:認証システムは無効です。');
public function execute(): Result {
if (!CURL_ENABLED) return Result::Error('エラー:認証システムは無効です。');
if (empty($this->url)) {
$this->responseError = 'URLがありません';
return \Result::Error($this->responseError);
return Result::Error($this->responseError);
}
// レスポンスデータのリセット
@@ -352,19 +355,18 @@ class Curl {
fwrite($this->stderr, "* 接続中: {$currentUrl}\n");
}
$parsed = parse_url($currentUrl);
if (!$parsed) {
$uri = new Uri($currentUrl);
if (!$uri) {
$this->responseError = "無効なURL: {$currentUrl}";
return \Result::Error($this->responseError);
return Result::Error($this->responseError);
}
$scheme = isset($parsed['scheme']) ? strtolower($parsed['scheme']) : 'http';
$host = $parsed['host'];
$port = isset($parsed['port'])
? $parsed['port'] : ($scheme === 'https' ? 443 : 80);
$path = isset($parsed['path']) ? $parsed['path'] : '/';
if (isset($parsed['query'])) {
$path .= '?'.$parsed['query'];
$scheme = $uri->getScheme() ?? 'http';
$port = $uri->getPort() ?? ($scheme === 'https' ? 443 : 80);
$path = $uri->getPath();
if ($path === '') $path = '/';
if (NULL !== $uri->getQuery()) {
$path .= '?'.$uri->getQuery();
}
// 認証
@@ -386,7 +388,7 @@ class Curl {
if ($method === 'POST' || $method === 'PUT') {
if (!empty($this->postRaw)) {
$httpData = $this->postRaw;
} elseif (!empty($this->postFields)) {
} else if (!empty($this->postFields)) {
$httpData = http_build_query($this->postFields);
if (!isset($this->headers['Content-Type'])) {
$this->headers['Content-Type'] = 'application/x-www-form-urlencoded';
@@ -402,38 +404,27 @@ class Curl {
}
$request = "{$method} {$path} HTTP/1.1\r\n";
$request .= "Host: {$host}\r\n";
$request .= "Host: {$uri->getHost()}\r\n";
$request .= "User-Agent: {$this->userAgent}\r\n";
$request .= "{$accept}\r\n";
$request .= "Connection: close\r\n";
// 認証ヘッダー
if (!empty($authHeader)) {
$request .= $authHeader;
}
// ヘッダーを追加
foreach ($this->headers as $name => $value) {
$request .= "{$name}: {$value}\r\n";
}
// リファラーが設定されていれば追加
if (!empty($this->referer) && !isset($this->headers['Referer'])) {
$request .= "Referer: {$this->referer}\r\n";
}
// クッキーヘッダーを追加
if (!empty($this->cookies) && !isset($this->headers['Cookie'])) {
$cookieStrings = [];
foreach ($this->cookies as $name => $value) {
$cookieStrings[] = $name.'='.urlencode($value);
}
$request .= 'Cookie: '.implode('; ', $cookieStrings)."\r\n";
$ext = $this->buildHeaderString();
if (!empty($ext)) {
$request .= $ext;
}
// ヘッダー終了
$request .= "\r\n";
// POSTデータを追加
if ($method === 'POST' || $method === 'PUT') {
if ($httpData !== '') {
$request .= $httpData;
}
@@ -460,7 +451,7 @@ class Curl {
$context = stream_context_create(['ssl' => $sslOptions]);
$socket = @stream_socket_client(
"tls://{$host}:{$port}",
"tls://{$uri->getHost()}:{$port}",
$errno,
$errstr,
$this->timeout,
@@ -468,7 +459,7 @@ class Curl {
$context
);
} else {
$socket = @fsockopen($host, $port, $errno, $errstr, $this->timeout);
$socket = @fsockopen($uri->getHost(), $port, $errno, $errstr, $this->timeout);
}
if (!$socket) {
@@ -476,7 +467,7 @@ class Curl {
if ($this->verbose && $this->stderr) {
fwrite($this->stderr, "* エラー: {$this->responseError}\n");
}
return \Result::Error($this->responseError);
return Result::Error($this->responseError);
}
// タイムアウトを設定
@@ -497,7 +488,7 @@ class Curl {
if ($rawResponse === '') {
$this->responseError = 'リスポンスエラー';
return \Result::Error($this->responseError);
return Result::Error($this->responseError);
}
list($headerLines, $body) = $this->parseResponse($rawResponse);
@@ -505,6 +496,7 @@ class Curl {
// ヘッダーを解析
$this->responseHeaders = [];
$this->responseCode = 0;
$redirectUrl = '';
foreach ($headerLines as $index => $header) {
if ($index === 0) {
@@ -518,6 +510,8 @@ class Curl {
$name = trim($name);
$value = trim($value);
$this->responseHeaders[$name] = $value;
$redirectUrl = $this->checkReds($name, $value, $currentUrl);
}
}
@@ -539,27 +533,30 @@ class Curl {
}
// リダイレクトが必要な場合
if (!empty($redirectUrl) && $redirectCount < $this->maxRedirects) {
if ($redirectUrl !== '' && $redirectCount < $this->maxRedirects) {
$currentUrl = $redirectUrl;
$redirectCount++;
$this->info['redirect_count'] = $redirectCount;
$this->info['redirect_url'] = $redirectUrl;
// 302や303リダイレクトはGETにメソッドを変更
if ($this->responseCode == 302 || $this->responseCode == 303) {
if (in_array($this->responseCode, [302, 303], true)) {
$this->method = 'GET';
$this->postRaw = '';
$this->postFields = [];
}
} else {
break;
continue;
}
break;
} while (true);
// リクエスト完了後、元のメソッドに戻す
$this->method = $originalMethod;
$this->info['total_time'] = microtime(true) - $startTime;
return \Result::Success();
return Result::Success();
}
/**
@@ -627,22 +624,20 @@ class Curl {
* @return string リダイレクトURL、リダイレクトがない場合は空文字
*/
private function checkReds(string $name, string $value, string $currentUrl): string {
$redirectUrl = '';
if (!$this->followRedirects) return '';
if (strtolower($name) !== 'location') return '';
if ($this->responseCode < 300 || $this->responseCode >= 400) return '';
$redirectUrl = $value;
if ($this->followRedirects && (strtolower($name) === 'location'
&& $this->responseCode >= 300 && $this->responseCode < 400)) {
$redirectUrl = $value;
if (strpos($redirectUrl, 'http') !== 0) {
if ($redirectUrl[0] === '/') {
$parsed = parse_url($currentUrl);
$redirectUrl = $parsed['scheme'].'://'.$parsed['host']
.(isset($parsed['port']) ? ':'.$parsed['port'] : '')
.$redirectUrl;
} else {
$redirectUrl = dirname($currentUrl).'/'.$redirectUrl;
}
}
if (strpos($redirectUrl, 'http') !== 0) {
if ($redirectUrl[0] === '/') {
$uri = new Uri($currentUrl);
$redirectUrl = $uri->getScheme().'://'.$uri->getHost()
.(NULL !== $uri->getPort() ? ':'.$uri->getPort() : '')
.$redirectUrl;
} else {
$redirectUrl = dirname($currentUrl).'/'.$redirectUrl;
}
}
return $redirectUrl;
@@ -654,16 +649,16 @@ class Curl {
* @return string 構築されたヘッダー文字列
*/
private function buildHeaderString(): string {
$headers = [];
$request = '';
// ユーザー指定のヘッダーを追加
foreach ($this->headers as $name => $value) {
$headers[] = "{$name}: {$value}";
$request .= "{$name}: {$value}\r\n";
}
// リファラーが設定されていれば追加
if (!empty($this->referer) && !isset($this->headers['Referer'])) {
$headers[] = "Referer: {$this->referer}";
$request .= "Referer: {$this->referer}\r\n";
}
// 必要に応じてクッキーヘッダーを追加
@@ -673,10 +668,10 @@ class Curl {
$cookieStrings[] = $name.'='.urlencode($value);
}
$headers[] = 'Cookie: '.implode('; ', $cookieStrings);
$request .= 'Cookie: '.implode('; ', $cookieStrings)."\r\n";
}
return implode("\r\n", $headers)."\r\n";
return $request;
}
/**

View File

@@ -38,6 +38,7 @@ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
namespace Std\Lib\Image;
use Std\Lib\Image\ImageInterface;
use LogType;
class Png implements ImageInterface {
public \stdClass $IHDR; // 画像ヘッダー
@@ -629,7 +630,7 @@ exif_crc:
fclose($fp);
$err = '不明なチャンク:'.$nextChunk.'、HEX'.bin2hex($nextChunk);
kys($err);
logger(\LogType::Image, '【PNG】'.$err);
logger(LogType::Image, '【PNG】'.$err);
return $png;
}

View File

@@ -37,6 +37,8 @@ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace Std\Lib;
use LogType;
class Mailer {
private $socket;
private string $host;
@@ -70,7 +72,7 @@ class Mailer {
$this->socket = fsockopen($this->host, $this->port, $errno, $err, 30);
if (!$this->socket) {
$msg = "接続に失敗: {$err} ({$errno})";
logger(\LogType::Mailer, $msg);
logger(LogType::Mailer, $msg);
throw new \Exception($msg);
}
@@ -104,7 +106,7 @@ class Mailer {
if (!stream_socket_enable_crypto(
$this->socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
$msg = "TLSハンドシェイクに失敗";
logger(\LogType::Mailer, $msg);
logger(LogType::Mailer, $msg);
throw new \Exception($msg);
}
@@ -198,7 +200,7 @@ class Mailer {
$response = $this->readResponse();
if (substr($response, 0, 3) != '250') {
$msg = "メール送信に失敗: {$response}";
logger(\LogType::Mailer, $msg);
logger(LogType::Mailer, $msg);
throw new \Exception($msg);
}
}
@@ -235,7 +237,7 @@ class Mailer {
$res = $this->readResponse();
if (substr($res, 0, 3) != $retcode) {
$msg = "{$command}」に対する予期しないレスポンス: {$res}";
logger(\LogType::Mailer, $msg);
logger(LogType::Mailer, $msg);
throw new \Exception($msg);
}

View File

@@ -52,6 +52,7 @@ class Openprovider {
private string $ip = '';
private int $resellerId = 0;
private int $lastAuth = 0;
// private string $BASEURL = 'https://api.openprovider.eu/v1beta';
private string $BASEURL = DEBUG_MODE ? 'http://api.sandbox.openprovider.nl:8480/v1beta'
: 'https://api.openprovider.eu/v1beta';
@@ -63,6 +64,10 @@ class Openprovider {
$this->cache = new Cache($this->cacheDir);
}
public function deleteCache(string $name): void {
$this->cache->murder($name);
}
/**
* トークンの受け取り。
* このライブリリーを使ったら、一回「login()」を実行する事が必須となります。
@@ -146,7 +151,6 @@ class Openprovider {
/**
* ドメイン一覧。
* @todo テスト
*
* @return Result 結果。
*/
@@ -156,11 +160,12 @@ class Openprovider {
$cache = $this->cache->get($cacheName);
if ($this->cache->resurrect($cache, $query)) return Result::Success('', $cache);
$curl = $this->setupCurl('/domains');
$uri = "/domains?".http_build_query($query, '', '&', PHP_QUERY_RFC3986);
$curl = $this->setupCurl($uri);
$res = $this->curlResult($curl);
if (isset($res['data']['results'])) {
if (isset($res->data['data']['results'])) {
$this->cache->set($cacheName, $res->data);
return Result::Success('', $res['data']['results']);
return Result::Success('', $res->data['data']['results']);
}
return Result::Error('ドメインの確認に失敗。');
@@ -212,7 +217,8 @@ class Openprovider {
'autorenew' => $autorenew,
];
$curl = $this->setupCurl('/domains/', 'POST', $payload);
$uri = '/domains/';
$curl = $this->setupCurl($uri, 'POST', $payload);
kys('TODO');
$res = $this->curlResult($curl);
if (isset($res['data'])) return Result::Success('', $res);
@@ -249,7 +255,8 @@ class Openprovider {
'with_price' => $with_price,
];
$curl = $this->setupCurl('/domains/check', 'POST', $payload);
$uri = '/domains/check';
$curl = $this->setupCurl($uri, 'POST', $payload);
$res = $this->curlResult($curl);
if (isset($res->data['data']['results'])) return Result::Success('', $res->data['data']['results']);
@@ -266,7 +273,8 @@ class Openprovider {
public function suggestDomainname(array $payload = []): Result {
if (!OPENPROVIDER_ENABLED) return Result::error('エラーOpenProviderは無効です。');
$curl = $this->setupCurl('/domains/suggest-name', 'POST', $payload);
$uri = '/domains/suggest-name';
$curl = $this->setupCurl($uri, 'POST', $payload);
$res = $this->curlResult($curl);
if (isset($res->data['data']['results'])) {
return Result::Success('ドメインを勧められる事に成功。', $res->data['data']['results']);
@@ -285,7 +293,8 @@ class Openprovider {
public function tradeDomainname(array $payload = []): Result {
if (!OPENPROVIDER_ENABLED) return Result::error('エラーOpenProviderは無効です。');
$curl = $this->setupCurl('/domains/trade', 'POST', $payload);
$uri = '/domains/trade';
$curl = $this->setupCurl($uri, 'POST', $payload);
$res = $this->curlResult($curl);
if (isset($res->data['data']['results'])) {
$this->cache->murder('listdomains');
@@ -305,7 +314,8 @@ class Openprovider {
public function transferDomainname(array $payload = []): Result {
if (!OPENPROVIDER_ENABLED) return Result::error('エラーOpenProviderは無効です。');
$curl = $this->setupCurl('/domains/transfer', 'POST', $payload);
$uri = '/domains/transfer';
$curl = $this->setupCurl($uri, 'POST', $payload);
$res = $this->curlResult($curl);
if (isset($res->data['data']['results'])) {
$this->cache->murder('listdomains');
@@ -693,7 +703,6 @@ class Openprovider {
/**
* 請求書一覧
* @todo テスト
*
* @param array $query 検索クエリー
* @return Result
@@ -722,7 +731,6 @@ class Openprovider {
/**
* 支払一覧
* @todo テスト
*
* @param array $query 検索クエリー
* @return Result
@@ -751,7 +759,6 @@ class Openprovider {
/**
* トランザクション一覧
* @todo テスト
*
* @param array $query 検索クエリー
* @return Result
@@ -2086,7 +2093,6 @@ class Openprovider {
/**
* リセラーの受け取り
* @todo テスト
*
* @param array $query 検索クエリー
* @return Result
@@ -2162,7 +2168,6 @@ class Openprovider {
/**
* 統計の受け取り
* @todo テスト
*
* @return Result
*/

View File

@@ -407,6 +407,7 @@ class Tester {
*/
public function assertArrayHasKey(mixed $key, array $array,
?string $message = null): Tester {
if (NULL === $key) throw new AssertionFailedException("配列鍵がありません");
if (!is_array($array) && !($array instanceof \ArrayAccess)) {
throw new AssertionFailedException(
'第2引数は配列又はArrayAccessを実装している必要があります');

View File

@@ -7,5 +7,5 @@ HTTPHOME=/var/www/htdocs/LittleBeast
rsync ${RSYNCOPT} *.php ${SRV}:${HTTPHOME}
rsync ${RSYNCOPT} blog ${SRV}:${HTTPHOME}
#rsync ${RSYNCOPT} config ${SRV}:${HTTPHOME}
rsync ${RSYNCOPT} src/Site/Controller ${SRV}:${HTTPHOME}/src/Site
rsync ${RSYNCOPT} src ${SRV}:${HTTPHOME}
rsync ${RSYNCOPT} view ${SRV}:${HTTPHOME}

View File

@@ -434,9 +434,40 @@ function getImageInfo(string $url): \Std\Lib\Image {
return $img;
}
// PHP 8.3と8.4の場合
if (!function_exists('array_last')) {
function array_last(array $array): mixed {
return $array === [] ? null : $array[array_key_last($array)];
}
function align(int $val): int {
if ($val <= 0) return 1;
if ($val === 1) return 1;
$res = 0;
$lower = 1;
while ($lower * 2 <= $val) $lower *= 2;
$upper = $lower * 2;
if (($val - $lower) <= ($upper - $val)) return $lower;
return $upper;
}
function align_up(int $val): int {
if ($val <= 0) return 1;
if ($val === 1) return 1;
$res = 0;
$lower = 1;
while ($lower * 2 <= $val) $lower *= 2;
$upper = $lower * 2;
return $upper;
}
function align_down(int $val): int {
if ($val <= 0) return 1;
if ($val === 1) return 1;
$res = 0;
$lower = 1;
while ($lower * 2 <= $val) $lower *= 2;
return $lower;
}

View File

@@ -20,7 +20,7 @@
{@ endif @}
{@ endif @}
<meta name="description" content="{{ $description }}" />
<meta name="keywords" content="{{ SITEINFO['tags'].(isset($meta->category) ? ',' : '') }}{@ if (isset($meta) && isset($meta->category)) @}{@ foreach ($meta->category as $k => $cat) @}{{ $cat.($k === array_key_last($meta->category) ? '' : ',') }}{@ endforeach @}{@ endif @}" />
<meta name="keywords" content="{{ SITEINFO['tags'].(isset($meta->category) ? ','.implode(',', $meta->category) : '') }}" />
<meta property="og:title" content="{{ SITEINFO['title'] }}: {{ $pagetit }}" />
<meta property="og:description" content="{{ $description }}" />

View File

@@ -1,6 +1,12 @@
{@ include(common/header) @}
<h1>{{ $description }}</h1>
<h1>お帰り、{{ $data['data']['name']['full_name'] }}</h1>
<ul>
<li>請求</li>
<ul>
<li><a href="/openprovider/listinvoices">請求書一覧</a></li>
<li><a href="/openprovider/listpayments">支払一覧</a></li>
<li><a href="/openprovider/listtransactions">トランザクション一覧</a></li>
</ul>
<li>顧客様</li>
<ul>
<li><a href="/openprovider/listcustomers">顧客様検索</a></li>
@@ -17,4 +23,39 @@
<li><a href="/openprovider/listdnszones">DNSゾーン一覧</a></li>
</ul>
</ul>
<div class="op-grid">
<div class="op-grid-item">
<div style="color: #00bb00; font-size: 24px;">利用可能な残高: € {{ $data['data']['balance'] }}</div>
<div style="color: #777; font-size: 11px;">使用残高: € {{ $data['data']['reserved_balance'] }}</div>
</div>
<div class="op-grid-item">
<a href="/openprovider/listdomains">ドメイン名</a>数:{{ $data['data']['statistics']['domain']['by_status']['ACT'] }}
</div>
<div class="op-grid-item">
<a href="/openprovider/listsslorders">SSL証明書</a>数:{{ $data['data']['statistics']['ssl']['by_status']['ACT'] }}
</div>
<div class="op-grid-item">
<a href="/openprovider/listlicenses">ライセンス</a>数:{{ $data['data']['statistics']['license']['total'] }}
</div>
</div>
<h2>間もなく更新が必要なドメイン名</h2>
<table>
<thead>
<tr>
<th>ドメイン名</th>
<th>更新日</th>
</tr>
</thead>
<tbody>
{@ foreach ($domain as $d) @}
<tr>
{$ $dom = $d['domain']['name'].'.'.$d['domain']['extension'] $}
<td><a href="/openprovider/getdomain/{{ $dom }}">{{ $dom }}</a></td>
<td>{{ $d['renewal_date'] }}</td>
</tr>
{@ endforeach @}
</tbody>
</table>
{@ include(common/footer) @}

View File

@@ -0,0 +1,31 @@
{@ include(common/header) @}
<h1>請求書一覧</h1>
{@ if (isset($data['data']['results'])) @}
<table>
<thead>
<tr>
<th>日時</th>
<th>請求書番号</th>
<th>単殻</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{@ foreach ($data['data']['results'] as $d) @}
<tr>
<td>{{ $d['creation_date'] }}</td>
<td>{{ $d['invoice_number'] }}</td>
<td>{{ $d['amount']['product']['price'].' '.$d['amount']['product']['currency'] }}</td>
<td><a href="{{ $d['invoice_url'] }}">請求書をダウンロード</a></td>
<td><a href="{{ $d['attachment_url'] }}">添付</a></td>
</tr>
{@ endforeach @}
</tbody>
</table>
<p>結果数:{{ $data['data']['total'] }}</p>
{@ include(common/pagination) @}
{@ else @}
<p>何も見つけられませんでした。</p>
{@ endif @}
{@ include(common/footer) @}

View File

@@ -0,0 +1,38 @@
{@ include(common/header) @}
<h1>支払一覧</h1>
{@ if (isset($data['data']['results'])) @}
<table>
<thead>
<tr>
<th>日時</th>
<th>支払ID</th>
<th>種類</th>
<th>単殻</th>
<th>支払済み</th>
<th>認証済み</th>
<th>完了</th>
</tr>
</thead>
<tbody>
{@ foreach ($data['data']['results'] as $d) @}
{$ $p = isset($d['payment_date']) $}
{$ $c = isset($d['confirmation_date']) $}
{$ $r = $d['is_processed'] == 1 $}
<tr>
<td>{{ $d['creation_date'] }}</td>
<td>{{ $d['id'] }}</td>
<td>{{ $d['type'] }}</td>
<td>{{ $d['amount']['product']['price'].' '.$d['amount']['product']['currency'] }}</td>
<td><span style="color: #{{ $p ? '14c014' : 'ee5040' }};">{{ $p ? '' : '✕' }}</span></td>
<td><span style="color: #{{ $c ? '14c014' : 'ee5040' }};">{{ $c ? '' : '✕' }}</span></td>
<td><span style="color: #{{ $r ? '14c014' : 'ee5040' }};">{{ $r ? '' : '✕' }}</span></td>
</tr>
{@ endforeach @}
</tbody>
</table>
<p>結果数:{{ $data['data']['total'] }}</p>
{@ include(common/pagination) @}
{@ else @}
<p>何も見つけられませんでした。</p>
{@ endif @}
{@ include(common/footer) @}

View File

@@ -0,0 +1,40 @@
{@ include(common/header) @}
<h1>トランザクション一覧</h1>
検索バー<br />
{@ if (isset($data['data']['results'])) @}
<table>
<thead>
<tr>
<th>日時</th>
<th>状況</th>
<th>ドメイン名・商品</th>
<th>数</th>
<th>値段</th>
<th>デビット</th>
<th>クレジット</th>
</tr>
</thead>
<tbody>
{@ foreach ($data['data']['results'] as $d) @}
{$ $pp = $d['price']['product'] $}
{$ $pr = $d['price']['reseller'] $}
{$ $tp = $d['total']['product'] $}
{$ $tr = $d['total']['reseller'] $}
<tr>
<td>{{ $d['creation_date'] }}</td>
<td>{{ $d['action'] }}</td>
<td>{{ $d['subject'] }}</td>
<td>{{ $d['quantity'] }}</td>
<td>{{ $pp['price'].' '.$pp['currency'] }}<br /><span style="color: #777777;">({{ $pr['price'].' '.$pr['currency'] }})</span></td>
<td><span style="color: #ee5040;">{{ $tr['price'].' '.$tr['currency'] }}</span></td>
<td><span style="color: #14c014;"></span></td>
</tr>
{@ endforeach @}
</tbody>
</table>
<p>結果数:{{ $data['data']['total'] }}</p>
{@ include(common/pagination) @}
{@ else @}
<p>何も見つけられませんでした。</p>
{@ endif @}
{@ include(common/footer) @}