diff --git a/app/Http/Controllers/Home/Contact.php b/app/Http/Controllers/Home/Contact.php
new file mode 100644
index 0000000..5db7e0d
--- /dev/null
+++ b/app/Http/Controllers/Home/Contact.php
@@ -0,0 +1,147 @@
+field = [
+ 'kenmei' => '',
+ 'adr' => '',
+ 'cat' => '',
+ 'cats' => [
+ '' => '',
+ 'bugreport' => 'バグ報告したい',
+ 'chat' => 'チャットサービス(XMPP、IRC、Mumble)について聞きたい',
+ 'social' => 'SNSサービス(Pleroma、PeerTube)について聞きたい',
+ 'privfront' => '代替SNSフロントサービス(Nitter、Invidious、Librarian、Searx)について聞きたい',
+ 'storage' => 'ストレージサービス(Gitea、Nextcloud)について聞きたい',
+ 'otherserv' => '076外サービス(テク諏訪、076萌、URLoli、some.very.questionable.website、hozon.site、xmr.jp等)について聞きたい',
+ 'scam1' => 'DMCA報告したい',
+ 'scam2' => '営業したい',
+ 'scam3' => '法律について',
+ 'scam4' => '税金について',
+ 'scam5' => '無駄な話',
+ ],
+ 'bunsyo' => '',
+ 'gpg' => '',
+ 'ruleapply' => false,
+ ];
+ }
+
+ public function index (Request $r) {
+ if (isset($r->submit)) return $this->send($r);
+ return view('pages.site.contact', ['field' => $this->field, 'err' => []]);
+ }
+
+ public function seiko () {
+ return view('pages.site.contact-seiko', ['field' => $this->field, 'suc' => ['メールを送りました!', '送信者様は連絡ルールを守ったら、24時間以内で返事します。']]);
+ }
+
+ public function send (Request $r) {
+ $this->field['adr'] = $r->adr;
+ $this->field['kenmei'] = $r->kenmei;
+ $this->field['cat'] = $r->cat;
+ $this->field['gpg'] = !isset($r->gpg) || is_null($r->gpg) || $r->gpg == '' ? null : file_get_contents($r->file('gpg'));
+ $this->field['bunsyo'] = $r->bunsyo;
+ $this->field['ruleapply'] = isset($r->ruleapply);
+
+ $err = [];
+ $gpg = null;
+
+ // メールアドレス
+ if (!isset($this->field['adr']) || is_null($this->field['adr']) || $this->field['adr'] == '') $err[] = 'メールアドレスをご入力下さい。';
+ else if (!filter_var($this->field['adr'], FILTER_VALIDATE_EMAIL)) $err[] = 'メールアドレスを正しくご入力下さい。';
+ $filename = trim($this->field['adr']).'.key';
+
+ // 件名
+ if (!isset($this->field['kenmei']) || is_null($this->field['kenmei']) || $this->field['kenmei'] == '') $err[] = '件名をご入力下さい。';
+ if (str_contains($this->field['kenmei'], 'http://') || str_contains($this->field['kenmei'], 'https://')) $err[] = '件名でURLを入らないで下さい。';
+ $this->field['bunsyo'] = trim($this->field['bunsyo']);
+
+ // カテゴリ
+ if (!isset($this->field['cat']) || is_null($this->field['cat']) || $this->field['cat'] == '') $err[] = 'カテゴリをご選択下さい。';
+
+ // GPG
+ if (!isset($this->field['gpg']) || is_null($this->field['gpg']) || $this->field['gpg'] == '') $err[] = 'GPGをご選択下さい。';
+ else {
+ $gpg = new \gnupg();
+ $info = $gpg->import($this->field['gpg']);
+ $gpg->addencryptkey($info['fingerprint']);
+ Storage::disk('public')->put($filename, $this->field['gpg']);
+ $path = Storage::disk('public')->path($filename);
+ $verifygpg = explode("\n", $this->run('gpg --dry-run --import '.$path))[0];
+ if (!str_contains($verifygpg, '処理数の合計: 1')) $err[] = $verifygpg;
+ }
+
+ // 文章
+ if (!isset($this->field['bunsyo']) || is_null($this->field['bunsyo']) || $this->field['bunsyo'] == '') $err[] = '文章をご入力下さい。';
+ if (str_contains($this->field['bunsyo'], 'http://') || str_contains($this->field['bunsyo'], 'https://')) $err[] = '文章でURLを入らないで下さい。';
+ $this->field['bunsyo'] = $gpg->encrypt(trim($this->field['bunsyo']));
+
+ // 連絡ルール
+ if (!$this->field['ruleapply']) $err[] = 'ルールを同意して下さい。';
+
+ if (!empty($err)) {
+ if (isset($this->field['gpg']) && !is_null($this->field['gpg']) && $this->field['gpg'] != '') Storage::disk('public')->delete($filename);
+ return view('pages.site.contact', ['field' => $this->field, 'err' => $err]);
+ }
+
+ // カテゴリはDMCA報告、営業、税金、法律、又は無駄な話を選択したら、いつでも送信せず失敗します。
+ if (str_contains($this->field['cat'], 'scam')) {
+ Storage::disk('public')->delete($filename);
+ return view('pages.site.contact', ['field' => $this->field, 'err' => ['送信に失敗しました。数時間後もう一回送信してみて下さい。']]);
+ }
+
+ // メールを送る
+ try {
+ Mail::to(config('mail.from.address'))->send(new ContactNotifyMail($this->field, $filename));
+ } catch (\Throwable $e) {
+ Storage::disk('public')->delete($filename);
+ Log::critical($e);
+ return view('pages.site.contact', ['field' => $this->field, 'err' => ['送信に失敗しました。数時間後もう一回送信してみて下さい。']]);
+ }
+
+ Storage::disk('public')->delete($filename);
+ $this->field['kenmei'] = '';
+ $this->field['adr'] = '';
+ $this->field['cat'] = '';
+ $this->field['bunsyo'] = '';
+ $this->field['gpg'] = '';
+ $this->field['ruleapply'] = false;
+
+ return redirect('/contact/seiko');
+ }
+
+ function run ($bin, $command = '', $force = true) {
+ $stream = null;
+ $bin .= $force ? ' 2>&1' : '';
+
+ $descriptorSpec = array(
+ 0 => array('pipe', 'r'),
+ 1 => array('pipe', 'w')
+ );
+
+ $process = proc_open($bin, $descriptorSpec, $pipes);
+
+ if (is_resource($process)) {
+ fwrite($pipes[0], $command);
+ fclose($pipes[0]);
+
+ $stream = stream_get_contents($pipes[1]);
+ fclose($pipes[1]);
+
+ proc_close($process);
+ }
+
+ return $stream;
+ }
+}
\ No newline at end of file
diff --git a/app/Mail/ContactNotifyMail.php b/app/Mail/ContactNotifyMail.php
new file mode 100644
index 0000000..2dd7de8
--- /dev/null
+++ b/app/Mail/ContactNotifyMail.php
@@ -0,0 +1,23 @@
+form = $form;
+ $this->file = $file;
+ }
+
+ public function build () {
+ return $this->from($this->form['adr'])->subject($this->form['kenmei'])->markdown('emails.notify.contact', ['form' => $this->form])->attachFromStorage('public/'.$this->file);
+ }
+}
diff --git a/config/filesystems.php b/config/filesystems.php
index 94c8112..2ea896a 100644
--- a/config/filesystems.php
+++ b/config/filesystems.php
@@ -55,6 +55,13 @@ return [
'visibility' => 'public',
],
+ 'private' => [
+ 'driver' => 'local',
+ 'root' => storage_path('app/private'),
+ 'url' => env('APP_URL').'/storage',
+ 'visibility' => 'private',
+ ],
+
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
diff --git a/config/mail.php b/config/mail.php
index d67deb6..75f350e 100644
--- a/config/mail.php
+++ b/config/mail.php
@@ -41,6 +41,7 @@ return [
'encryption' => env('MAIL_ENCRYPTION', 'tls'),
'username' => env('MAIL_USERNAME'),
'password' => env('MAIL_PASSWORD'),
+ 'sendmail' => '/usr/sbin/sendmail -bs',
'timeout' => null,
'auth_mode' => null,
],
@@ -87,6 +88,10 @@ return [
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
'name' => env('MAIL_FROM_NAME', 'Example'),
],
+ 'owner' => [
+ 'address' => env('MAIL_OWNER_ADDRESS', 'hello@example.com'),
+ 'name' => env('MAIL_OWNER_NAME', 'Example'),
+ ],
/*
|--------------------------------------------------------------------------
diff --git a/resources/views/emails/notify/contact.blade.php b/resources/views/emails/notify/contact.blade.php
new file mode 100644
index 0000000..ffcf7dd
--- /dev/null
+++ b/resources/views/emails/notify/contact.blade.php
@@ -0,0 +1,10 @@
+@component('mail::message')
+# {{ $form['kenmei'] }}
+
+{{ $form['adr'] }}
+{{ $form['cat'] }}
+
+□□□□□□□□□□□□□□□□□□□□
+
+{{ nl2br($form['bunsyo']) }}
+@endcomponent
diff --git a/resources/views/pages/site/contact-seiko.blade.php b/resources/views/pages/site/contact-seiko.blade.php
new file mode 100644
index 0000000..df20af4
--- /dev/null
+++ b/resources/views/pages/site/contact-seiko.blade.php
@@ -0,0 +1,15 @@
+@extends('theme.'.env('THEME').'.site')
+
+@section('content')
+