- 의견 창 헤더 드래그 이동, 우하단 모서리 크기 조절(resize), 비차단 플로팅 - 입력 글자수 1000→2000자, '0/2000' 카운터 표시 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
99 lines
4.2 KiB
PHP
99 lines
4.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Controllers;
|
|
|
|
use App\Models\FeedbackFileModel;
|
|
use App\Models\FeedbackModel;
|
|
use CodeIgniter\HTTP\ResponseInterface;
|
|
|
|
/**
|
|
* 사용자 의견 접수. 로그인 사용자가 화면에서 남긴 의견 + 스크린샷을 저장한다.
|
|
* (계정·지자체는 클라이언트 값을 믿지 않고 세션에서 채운다.)
|
|
*/
|
|
class Feedback extends BaseController
|
|
{
|
|
public function store(): ResponseInterface
|
|
{
|
|
if (! session()->get('logged_in')) {
|
|
return $this->response->setStatusCode(401)->setJSON(['ok' => false, 'message' => '로그인이 필요합니다.']);
|
|
}
|
|
|
|
$content = trim((string) $this->request->getPost('content'));
|
|
if ($content === '') {
|
|
return $this->response->setJSON(['ok' => false, 'message' => '의견 내용을 입력해 주세요.']);
|
|
}
|
|
if (mb_strlen($content) > 2000) {
|
|
$content = mb_substr($content, 0, 2000);
|
|
}
|
|
|
|
helper('admin');
|
|
$lgIdx = function_exists('admin_effective_lg_idx') ? admin_effective_lg_idx() : null;
|
|
if (! $lgIdx) {
|
|
$raw = session()->get('mb_lg_idx');
|
|
$lgIdx = ($raw !== null && $raw !== '') ? (int) $raw : null;
|
|
}
|
|
|
|
$model = model(FeedbackModel::class);
|
|
$id = $model->insert([
|
|
'fb_lg_idx' => $lgIdx,
|
|
'fb_mb_idx' => (int) session()->get('mb_idx'),
|
|
'fb_mb_name' => (string) (session()->get('mb_name') ?? ''),
|
|
'fb_mb_level' => (int) session()->get('mb_level'),
|
|
'fb_content' => $content,
|
|
'fb_page_url' => mb_substr((string) $this->request->getPost('page_url'), 0, 255),
|
|
'fb_page_title' => mb_substr((string) $this->request->getPost('page_title'), 0, 255),
|
|
'fb_user_agent' => mb_substr((string) $this->request->getHeaderLine('User-Agent'), 0, 255),
|
|
'fb_status' => 'new',
|
|
], true);
|
|
|
|
if (! $id) {
|
|
return $this->response->setJSON(['ok' => false, 'message' => '저장 중 오류가 발생했습니다.']);
|
|
}
|
|
|
|
$dir = WRITEPATH . 'uploads/feedback/';
|
|
if (! is_dir($dir)) {
|
|
@mkdir($dir, 0775, true);
|
|
}
|
|
$fileModel = model(FeedbackFileModel::class);
|
|
|
|
// 1) 화면 캡처(dataURL) — 실패해도 의견 본문은 이미 저장됨
|
|
$shot = (string) $this->request->getPost('screenshot');
|
|
if ($shot !== '' && preg_match('#^data:image/(png|jpeg);base64,#', $shot, $m) === 1) {
|
|
$ext = $m[1] === 'png' ? 'png' : 'jpg';
|
|
$bin = base64_decode(substr($shot, strpos($shot, ',') + 1), true);
|
|
if ($bin !== false && strlen($bin) <= 5 * 1024 * 1024) {
|
|
$file = 'fb_' . (int) $id . '_cap_' . bin2hex(random_bytes(4)) . '.' . $ext;
|
|
if (@file_put_contents($dir . $file, $bin) !== false) {
|
|
$model->update($id, ['fb_screenshot' => $file]); // 목록 캡처여부 표시용
|
|
$fileModel->insert(['ff_fb_idx' => $id, 'ff_kind' => 'capture', 'ff_path' => $file, 'ff_mime' => 'image/' . ($ext === 'png' ? 'png' : 'jpeg')]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2) 사용자가 첨부한 이미지(여러 장)
|
|
$allowed = ['image/png' => 'png', 'image/jpeg' => 'jpg', 'image/gif' => 'gif', 'image/webp' => 'webp'];
|
|
$count = 0;
|
|
foreach ($this->request->getFileMultiple('files') ?? [] as $f) {
|
|
if ($count >= 5 || $f === null || ! $f->isValid() || $f->hasMoved()) {
|
|
continue;
|
|
}
|
|
$mime = (string) $f->getMimeType();
|
|
if (! isset($allowed[$mime]) || $f->getSize() > 5 * 1024 * 1024) {
|
|
continue;
|
|
}
|
|
$file = 'fb_' . (int) $id . '_up_' . bin2hex(random_bytes(4)) . '.' . $allowed[$mime];
|
|
try {
|
|
$f->move($dir, $file);
|
|
$fileModel->insert(['ff_fb_idx' => $id, 'ff_kind' => 'upload', 'ff_path' => $file, 'ff_mime' => $mime]);
|
|
$count++;
|
|
} catch (\Throwable $e) {
|
|
// 개별 파일 실패는 건너뜀
|
|
}
|
|
}
|
|
|
|
return $this->response->setJSON(['ok' => true]);
|
|
}
|
|
}
|