<?php
declare(strict_types=1);

/**
 * ============================================================
 *  お問い合わせフォーム（簡易版）  form-lite  ＝ 無料版同梱用
 * ------------------------------------------------------------
 *  固定3項目（お名前／メールアドレス／お問い合わせ内容）＋メール通知（任意で自動返信）。
 *  項目ビルダー・複数フォーム・添付ファイル・受信箱・営業フィルタは「フル版」の機能で、
 *  簡易版には入っていない（＝コードに無いので解除フラグでは開けられない）。
 *  フル版 plugins/form/ は別物として配布し、購入者がDLして差し替える（このフォルダは削除）。
 *
 *  コア規約準拠：PDOプリペアド／h()／csrf／cms_safe_url。
 *  多層防御：CSRF＋署名付き時間ゲート＋ハニーポット＋IP/サブネット連投制限。
 * ============================================================ */

/** 固定の入力項目（3つ）。フル版のような自由定義は不可。 */
function formlite_fields(): array {
    return [
        ['key' => 'name',    'type' => 'text',     'label' => 'お名前'],
        ['key' => 'email',   'type' => 'email',    'label' => 'メールアドレス'],
        ['key' => 'message', 'type' => 'textarea', 'label' => 'お問い合わせ内容'],
    ];
}

/* ---------- 設定（settings テーブルに formlite_* で保存） ---------- */
function formlite_setting(string $key, ?string $default = null): ?string {
    return setting_get('formlite_' . $key, $default);
}
function formlite_setting_set(string $key, string $value): void {
    setting_set('formlite_' . $key, $value);
}
function formlite_flag(string $key, bool $default): bool {
    $v = formlite_setting($key, null);
    return $v === null ? $default : ($v === '1');
}

/** 送信先メール：設定 formlite_to → 無ければ最初の管理者メール。 */
function formlite_recipient(): string {
    $to = trim((string)(formlite_setting('to', '') ?? ''));
    if ($to !== '' && filter_var($to, FILTER_VALIDATE_EMAIL)) return $to;
    try {
        $em = (string)db()->query("SELECT email FROM admins ORDER BY id LIMIT 1")->fetchColumn();
        if ($em !== '' && filter_var($em, FILTER_VALIDATE_EMAIL)) return $em;
    } catch (Throwable $e) {}
    return '';
}

/* ---------- 署名付き時間ゲート（改ざん不可・サーバー保存不要） ---------- */
function formlite_secret(): string {
    $s = setting_get('formlite_secret', '') ?? '';
    if ($s === '') { $s = bin2hex(random_bytes(32)); setting_set('formlite_secret', $s); }
    return $s;
}
function formlite_ts_token(): string {
    $ts = (string)time();
    return $ts . '.' . hash_hmac('sha256', $ts, formlite_secret());
}
function formlite_ts_ok(string $token, int $minSec): bool {
    $p = explode('.', $token, 2);
    if (count($p) !== 2) return false;
    [$ts, $sig] = $p;
    if (!ctype_digit($ts)) return false;
    if (!hash_equals(hash_hmac('sha256', $ts, formlite_secret()), $sig)) return false;
    return (time() - (int)$ts) >= $minSec;
}

/* ---------- ハニーポット ---------- */
function formlite_honeypot_name(): string { return 'contact_url'; }

/* ---------- IP/サブネット連投制限（フル版と同じ data/form_rate を共用） ---------- */
function formlite_rate_ok(int $window = 3600, int $limitIp = 5, int $limitPfx = 30): bool {
    $ip = (string)($_SERVER['REMOTE_ADDR'] ?? '');
    if ($ip === '' || filter_var($ip, FILTER_VALIDATE_IP) === false) return true;
    $dir = CMS_DATA . '/form_rate';
    if (!is_dir($dir) && !@mkdir($dir, 0775, true) && !is_dir($dir)) return true;
    $file = $dir . '/rate.json';
    $now = time();
    $rate = is_file($file) ? json_array((string)@file_get_contents($file)) : [];

    $filter = function ($arr) use ($now, $window) {
        if (!is_array($arr)) return [];
        return array_values(array_filter($arr, fn($t) => is_int($t) && $t > $now - $window));
    };
    $pfx = null;
    if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
        $seg = explode('.', $ip); if (count($seg) === 4) $pfx = 'p4:' . $seg[0] . '.' . $seg[1] . '.' . $seg[2];
    } elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
        $bin = @inet_pton($ip); if ($bin !== false && strlen($bin) === 16) $pfx = 'p6:' . bin2hex(substr($bin, 0, 8));
    }
    $ipB  = $filter($rate[$ip] ?? null);
    $pfxB = $pfx !== null ? $filter($rate[$pfx] ?? null) : [];
    if (count($ipB) >= $limitIp) return false;
    if ($pfx !== null && count($pfxB) >= $limitPfx) return false;
    $ipB[] = $now; $rate[$ip] = $ipB;
    if ($pfx !== null) { $pfxB[] = $now; $rate[$pfx] = $pfxB; }
    if (mt_rand(1, 20) === 1) {
        foreach ($rate as $k => $arr) {
            $f = $filter($arr);
            if ($f === []) unset($rate[$k]); else $rate[$k] = $f;
        }
    }
    @file_put_contents($file, json_encode($rate, JSON_UNESCAPED_SLASHES), LOCK_EX);
    return true;
}

/* ---------- リダイレクト先の安全化（オープンリダイレクト対策） ---------- */
function formlite_safe_back(string $url): string {
    $url = trim($url);
    if ($url === '') return url('/');
    $u = @parse_url($url);
    if ($u === false) return url('/');
    $host = (string)($_SERVER['HTTP_HOST'] ?? '');
    if (isset($u['host']) && $host !== '' && strcasecmp($u['host'], $host) !== 0) return url('/');
    $path = (string)($u['path'] ?? '/');
    if ($path === '' || $path[0] !== '/') return url('/');
    if (strpos($path, '\\') !== false) return url('/');      // バックスラッシュ（ブラウザが / に正規化し //evil 化する）を弾く
    if (isset($path[1]) && $path[1] === '/') return url('/'); // // で始まるプロトコル相対URLを弾く
    $query = isset($u['query']) ? ('?' . $u['query']) : '';
    return $path . $query;
}

/* ---------- バリデーション（固定3項目・すべて必須） ---------- */
function formlite_validate(array $post): array {
    $errors = []; $values = [];
    foreach (formlite_fields() as $f) {
        $k = (string)$f['key']; $type = (string)$f['type']; $label = (string)$f['label'];
        $v = trim((string)($post[$k] ?? ''));
        $v = str_replace(["\r\n", "\r"], "\n", $v);
        if ($type !== 'textarea') $v = (string)preg_replace('/[\r\n]+/', ' ', $v);
        if ($v === '') {
            $errors[$k] = $label . 'を入力してください。';
        } elseif ($type === 'email' && !filter_var($v, FILTER_VALIDATE_EMAIL)) {
            $errors[$k] = $label . 'の形式が正しくありません。';
        } elseif ($type === 'text' && mb_strlen($v) > 100) {
            $errors[$k] = $label . 'が長すぎます（100文字以内）。';
        } elseif ($type === 'textarea' && mb_strlen($v) > 5000) {
            $errors[$k] = $label . 'が長すぎます（5000文字以内）。';
        }
        $values[$k] = $v;
    }
    return [$errors, $values];
}

/* ---------- フォーム入力欄HTML（全項目必須） ---------- */
function formlite_render_fields(array $values = [], array $errors = []): string {
    $h = '';
    foreach (formlite_fields() as $f) {
        $k = (string)$f['key']; $type = (string)$f['type']; $label = (string)$f['label'];
        $old = (string)($values[$k] ?? '');
        $err = isset($errors[$k]) ? '<span class="cms-form-fielderr">' . h((string)$errors[$k]) . '</span>' : '';
        $h .= '<div class="cms-form-row"><label class="cms-form-label">' . h($label) . ' <span class="cms-form-req">必須</span></label>';
        if ($type === 'textarea') {
            $h .= '<textarea class="cms-form-input" name="' . h($k) . '" rows="6" required>' . h($old) . '</textarea>';
        } else {
            $it = ($type === 'email') ? 'email' : 'text';
            $h .= '<input class="cms-form-input" type="' . $it . '" name="' . h($k) . '" value="' . h($old) . '" required>';
        }
        $h .= $err . '</div>';
    }
    return $h;
}

/* ---------- メール送信（From＝宛先・Reply-To＝送信者・UTF-8） ---------- */
function formlite_mail_header_safe(string $s): string { return trim((string)preg_replace('/[\r\n]+/', ' ', $s)); }

function formlite_send_mail(array $values): bool {
    if (cms_preview_mode()) return true; // 公開プレビュー版は実メール送信しない（送信された体で完了画面へ）
    $to = formlite_recipient();
    if ($to === '') return false;
    $subject = trim((string)(formlite_setting('subject', 'お問い合わせ') ?? 'お問い合わせ'));
    if ($subject === '') $subject = 'お問い合わせ';

    $lines = [];
    $lines[] = '【受信日時】' . date('Y-m-d H:i:s');
    if (!empty($_SERVER['HTTP_REFERER'])) $lines[] = '【送信元ページ】' . formlite_mail_header_safe((string)$_SERVER['HTTP_REFERER']);
    $lines[] = '';
    foreach (formlite_fields() as $f) {
        $lines[] = '【' . (string)$f['label'] . '】';
        $lines[] = (string)($values[(string)$f['key']] ?? '');
    }
    $body = implode("\n", $lines);

    $from  = $to; // From は固定（自ドメイン宛先想定）。送信者アドレスは Reply-To に。
    $reply = (string)($values['email'] ?? '');
    $headers = 'From: ' . formlite_mail_header_safe($from) . "\n";
    if ($reply !== '' && filter_var($reply, FILTER_VALIDATE_EMAIL)) $headers .= 'Reply-To: ' . formlite_mail_header_safe($reply) . "\n";
    $headers .= "MIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\n";
    mb_language('uni'); mb_internal_encoding('UTF-8');
    $env = filter_var($from, FILTER_VALIDATE_EMAIL) ? ('-f' . $from) : '';
    return $env !== '' ? mb_send_mail($to, $subject, $body, $headers, $env) : mb_send_mail($to, $subject, $body, $headers);
}

/* ---------- 自動返信（任意・送信者宛） ---------- */
function formlite_send_autoreply(array $values): void {
    if (cms_preview_mode()) return; // 公開プレビュー版は自動返信も送らない
    if (!formlite_flag('autoreply', false)) return;
    $to = (string)($values['email'] ?? '');
    if (!filter_var($to, FILTER_VALIDATE_EMAIL)) return;
    $subject = trim((string)(formlite_setting('autoreply_subject', 'お問い合わせを受け付けました') ?? ''));
    if ($subject === '') $subject = 'お問い合わせを受け付けました';
    $site  = trim((string)(setting_get('site_name', '') ?? ''));
    $intro = trim((string)(formlite_setting('autoreply_body', "このたびはお問い合わせありがとうございます。\n以下の内容で受け付けいたしました。") ?? ''));
    $sig   = trim((string)(formlite_setting('signature', '') ?? ''));
    if ($sig === '' && $site !== '') $sig = $site;

    $lines = [$intro, ''];
    foreach (formlite_fields() as $f) {
        $lines[] = '【' . (string)$f['label'] . '】';
        $lines[] = (string)($values[(string)$f['key']] ?? '');
    }
    if ($sig !== '') { $lines[] = ''; $lines[] = $sig; } // 区切り線は入れない（署名内で自由に）
    $body = implode("\n", $lines);

    $from = formlite_recipient();
    $headers = ($from !== '' ? 'From: ' . formlite_mail_header_safe($from) . "\n" : '')
             . "MIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\n";
    mb_language('uni'); mb_internal_encoding('UTF-8');
    $env = ($from !== '' && filter_var($from, FILTER_VALIDATE_EMAIL)) ? ('-f' . $from) : '';
    if ($env !== '') mb_send_mail($to, $subject, $body, $headers, $env); else mb_send_mail($to, $subject, $body, $headers);
}

/**
 * フォームの位置に戻るためのアンカーID。
 * 送信後の「ありがとうございました」やエラーが画面の外に出てしまわないよう、
 * 送信 → 元ページの「フォームがあった位置」へ戻すために使う（ページ下部やLPの末尾に置いたとき用）。
 */
function formlite_anchor_id(): string { return 'cms-form-contact'; }

/* ---------- 公開フォームパーツの描画（cms_register_part_renderer 'contact-form'） ---------- */
function formlite_render_part(array $data, array $def, array $ctx): array {
    $flash = $_SESSION['formlite_flash'] ?? null;
    unset($_SESSION['formlite_flash']);
    $errors    = is_array($flash['errors'] ?? null) ? $flash['errors'] : [];
    $old       = is_array($flash['old'] ?? null) ? $flash['old'] : [];
    $formError = (string)($flash['formError'] ?? '');
    $sent      = (isset($_GET['sent']) && (string)$_GET['sent'] === '1');

    return [
        'F_MISSING'   => false,
        'F_ACTION'    => url('/plugins/form-lite/submit.php'),
        'F_CSRF'      => csrf_token(),
        'F_TS'        => formlite_ts_token(),
        'F_HP'        => formlite_honeypot_name(),
        'F_BACK'      => (string)($_SERVER['REQUEST_URI'] ?? ''),
        'F_FORMKEY'   => 'contact',
        'F_ANCHOR'    => formlite_anchor_id(),   // 送信後にこの位置へ戻す
        'F_SENT'      => $sent,
        'F_HAS_ERR'   => ($errors !== [] || $formError !== ''),
        'F_FORMERR'   => $formError,
        'FORM_FIELDS' => formlite_render_fields($old, $errors),
    ];
}

/* ---------- 初回シード（contactページ＋パーツ＋メニュー）。テーブルは作らない。 ---------- */
function formlite_seed(PDO $pdo): void {
    $installed = $pdo->query("SELECT value FROM settings WHERE key='formlite_installed'")->fetchColumn();
    if ($installed === '1') return;

    $ex = $pdo->prepare("SELECT id FROM pages WHERE slug='contact'");
    $ex->execute();
    $pid = $ex->fetchColumn();
    if (!$pid) {
        $maxSort = (int)$pdo->query("SELECT COALESCE(MAX(sort),0) FROM pages")->fetchColumn();
        $pdo->prepare("INSERT INTO pages(slug,title,meta_description,parent,in_nav,sort) VALUES('contact','お問い合わせ','お問い合わせはこちらから。','',1,?)")
            ->execute([$maxSort + 1]);
        $pid = (int)$pdo->lastInsertId();
        $blockData = json_encode(['form_key' => 'contact'], JSON_UNESCAPED_UNICODE); // フル版へ差し替えても互換
        $pdo->prepare("INSERT INTO blocks(page_id,part,data,sort) VALUES(?, 'contact-form', ?, 0)")->execute([$pid, $blockData]);
    }
    $hasContactMenu = (int)$pdo->query("SELECT COUNT(*) FROM menu_items WHERE link_type='page' AND link='contact'")->fetchColumn();
    if ($hasContactMenu === 0) {
        $upd = $pdo->prepare("UPDATE menu_items SET link_type='page', link='contact' WHERE link_type='url' AND link='#' AND label LIKE '%お問い合わせ%'");
        $upd->execute();
        if ($upd->rowCount() === 0) {
            $mSort = (int)$pdo->query("SELECT COALESCE(MAX(sort),0) FROM menu_items")->fetchColumn();
            $pdo->prepare("INSERT INTO menu_items(label,link_type,link,parent_id,show_pc,show_mobile,is_button,sort) VALUES('お問い合わせ','page','contact',NULL,1,1,1,?)")
                ->execute([$mSort + 1]);
        }
    }
    $pdo->prepare("INSERT OR REPLACE INTO settings(key,value) VALUES('formlite_installed','1')")->execute();
}
