Files
jongryangje/app/Controllers/Feedback.php
taekyoungc 14b628b7ac feat: 의견 창 이동·크기 조절 + 글자수 2000자·카운터
- 의견 창 헤더 드래그 이동, 우하단 모서리 크기 조절(resize), 비차단 플로팅
- 입력 글자수 1000→2000자, '0/2000' 카운터 표시

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 15:48:43 +09:00

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]);
}
}