Initial commit - TPU Survey System with Google Forms & Certificate Generator
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* Authentication API
|
||||
* POST ?action=login — Login
|
||||
* POST ?action=logout — Logout
|
||||
* GET ?action=check — Check session
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
require_once __DIR__ . '/../config/auth.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$action = $_GET['action'] ?? $_POST['action'] ?? '';
|
||||
|
||||
switch ($action) {
|
||||
case 'login':
|
||||
handleLogin();
|
||||
break;
|
||||
case 'logout':
|
||||
handleLogout();
|
||||
break;
|
||||
case 'check':
|
||||
handleCheck();
|
||||
break;
|
||||
default:
|
||||
jsonResponse(['error' => 'Invalid action'], 400);
|
||||
}
|
||||
|
||||
function handleLogin() {
|
||||
$input = getInput();
|
||||
$username = trim($input['username'] ?? '');
|
||||
$password = trim($input['password'] ?? '');
|
||||
|
||||
if (empty($username) || empty($password)) {
|
||||
jsonResponse(['error' => 'กรุณากรอกชื่อผู้ใช้และรหัสผ่าน'], 400);
|
||||
}
|
||||
|
||||
$db = getDB();
|
||||
|
||||
// Try login by username
|
||||
$stmt = $db->prepare("SELECT * FROM users WHERE username = ? OR personnel_code = ? LIMIT 1");
|
||||
$stmt->execute([$username, $username]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if (!$user || !password_verify($password, $user['password_hash'])) {
|
||||
jsonResponse(['error' => 'ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง'], 401);
|
||||
}
|
||||
|
||||
// Set session
|
||||
$_SESSION['user_id'] = $user['id'];
|
||||
$_SESSION['username'] = $user['username'];
|
||||
$_SESSION['role'] = $user['role'];
|
||||
$_SESSION['full_name'] = $user['full_name'];
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'user' => [
|
||||
'id' => $user['id'],
|
||||
'username' => $user['username'],
|
||||
'role' => $user['role'],
|
||||
'full_name' => $user['full_name']
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
function handleLogout() {
|
||||
session_destroy();
|
||||
jsonResponse(['success' => true, 'message' => 'ออกจากระบบสำเร็จ']);
|
||||
}
|
||||
|
||||
function handleCheck() {
|
||||
if (isLoggedIn()) {
|
||||
jsonResponse([
|
||||
'loggedIn' => true,
|
||||
'user' => getCurrentUser()
|
||||
]);
|
||||
} else {
|
||||
jsonResponse(['loggedIn' => false]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
/**
|
||||
* Certificate Generator Helper using TCPDF
|
||||
* TPU Survey System
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
/**
|
||||
* Generates a PDF certificate with the recipient's name
|
||||
*
|
||||
* @param string $name Name to print on the certificate
|
||||
* @param string $outputPath Path to save the generated PDF. If empty, returns raw PDF string.
|
||||
* @param int $yPos Vertical offset (in points) where the name should be drawn
|
||||
* @return string Path to the file, or raw PDF string
|
||||
*/
|
||||
function generateCertificate($name, $outputPath = '', $yPos = 82.5, $certCode = '', $bgImage = '', $codeY = 190.5) {
|
||||
// สร้างเอกสาร PDF ขนาดมาตรฐาน A4 แนวนอน (297 x 210 มิลลิเมตร)
|
||||
$pdf = new TCPDF('L', 'mm', 'A4', true, 'UTF-8', false);
|
||||
|
||||
// Set document information
|
||||
$pdf->SetCreator(PDF_CREATOR);
|
||||
$pdf->SetAuthor('TPU Survey System');
|
||||
$pdf->SetTitle('เกียรติบัตร - ' . $name);
|
||||
$pdf->SetSubject('เกียรติบัตรผู้ผ่านการอบรม');
|
||||
|
||||
// Remove default header/footer
|
||||
$pdf->setPrintHeader(false);
|
||||
$pdf->setPrintFooter(false);
|
||||
|
||||
// Set margins to 0
|
||||
$pdf->SetMargins(0, 0, 0);
|
||||
$pdf->SetAutoPageBreak(false, 0);
|
||||
|
||||
// Add page
|
||||
$pdf->AddPage();
|
||||
|
||||
// Background image path
|
||||
$imgFile = __DIR__ . '/../pic/AI Antigravity.png';
|
||||
if (!empty($bgImage)) {
|
||||
$customImgFile = __DIR__ . '/../' . $bgImage;
|
||||
if (file_exists($customImgFile)) {
|
||||
$imgFile = $customImgFile;
|
||||
}
|
||||
}
|
||||
|
||||
if (!file_exists($imgFile)) {
|
||||
throw new Exception("ไม่พบไฟล์เทมเพลตเกียรติบัตรที่: " . $imgFile);
|
||||
}
|
||||
|
||||
// วาดภาพพื้นหลังให้พอดีกับกระดาษ A4 แนวนอน (297 x 210 มิลลิเมตร)
|
||||
$pdf->Image($imgFile, 0, 0, 297, 210, '', '', '', false, 300, '', false, false, 0);
|
||||
|
||||
// เรียกใช้ฟอนต์ภาษาไทย Sarabun
|
||||
$fontRegular = 'sarabun';
|
||||
$fontBold = 'sarabunb';
|
||||
|
||||
// กำหนดฟอนต์ชื่อ: หนา (Bold) ขนาด 26pt (สเกลย่อส่วนสำหรับ A4)
|
||||
$pdf->SetFont($fontBold, '', 26);
|
||||
$pdf->SetTextColor(30, 41, 59);
|
||||
|
||||
// จัดวางชื่อกึ่งกลางหน้ากระดาษ (ความกว้าง A4 = 297 มม.) (และแก้ปัญหาสระลอย)
|
||||
$pdf->SetXY(0, $yPos);
|
||||
$pdf->Cell(297, 15, fixThaiVowels($name), 0, false, 'C', 0, '', 0, false, 'T', 'M');
|
||||
|
||||
// วาดรหัสตรวจสอบเกียรติบัตรที่กึ่งกลางล่างสุดของกระดาษ (ขนาด 8pt พิกัด Y = $codeY มม.)
|
||||
if (!empty($certCode)) {
|
||||
$pdf->SetFont($fontRegular, '', 8);
|
||||
$pdf->SetTextColor(100, 116, 139); // Muted Slate color
|
||||
$pdf->SetXY(0, $codeY);
|
||||
$pdf->Cell(297, 10, fixThaiVowels('รหัสตรวจสอบเกียรติบัตร: ' . $certCode), 0, false, 'C', 0, '', 0, false, 'T', 'M');
|
||||
}
|
||||
|
||||
// Output
|
||||
if (!empty($outputPath)) {
|
||||
$dir = dirname($outputPath);
|
||||
if (!is_dir($dir)) {
|
||||
mkdir($dir, 0777, true);
|
||||
}
|
||||
$pdf->Output($outputPath, 'F');
|
||||
return $outputPath;
|
||||
} else {
|
||||
return $pdf->Output('', 'S');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* แก้ไขปัญหาสระลอยและวรรณยุกต์ซ้อนทับกันในไฟล์ PDF ภาษาไทย (สำหรับ TCPDF/FPDF)
|
||||
* โดยการใช้รหัส Unicode ในส่วน Private Use Area (PUA) ของฟอนต์ไทยมาตรฐาน
|
||||
*
|
||||
* @param string $text ข้อความภาษาไทยที่ต้องการจัดรูปแบบสระ/วรรณยุกต์
|
||||
* @return string ข้อความที่จัดตำแหน่งสระ/วรรณยุกต์แล้ว
|
||||
*/
|
||||
function fixThaiVowels($text) {
|
||||
// TCPDF with the Sarabun font natively handles Thai vowel and tone mark positioning correctly.
|
||||
// The previous PUA mapping caused square box rendering issues.
|
||||
return $text;
|
||||
}
|
||||
+329
@@ -0,0 +1,329 @@
|
||||
<?php
|
||||
/**
|
||||
* Export API
|
||||
* GET ?action=pdf&survey_id=X — Export to PDF
|
||||
* GET ?action=word&survey_id=X — Export to Word (DOCX)
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
require_once __DIR__ . '/../config/auth.php';
|
||||
|
||||
requireLogin();
|
||||
|
||||
$action = $_GET['action'] ?? '';
|
||||
$surveyId = (int)($_GET['survey_id'] ?? 0);
|
||||
|
||||
if (!$surveyId) {
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
jsonResponse(['error' => 'Missing survey_id'], 400);
|
||||
}
|
||||
|
||||
// Check access
|
||||
$db = getDB();
|
||||
if (!isAdmin()) {
|
||||
$stmt = $db->prepare("SELECT user_id FROM surveys WHERE id = ?");
|
||||
$stmt->execute([$surveyId]);
|
||||
$survey = $stmt->fetch();
|
||||
if (!$survey || $survey['user_id'] != $_SESSION['user_id']) {
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
jsonResponse(['error' => 'ไม่มีสิทธิ์'], 403);
|
||||
}
|
||||
}
|
||||
|
||||
// Get statistics data
|
||||
$statsData = getStatsData($surveyId);
|
||||
|
||||
switch ($action) {
|
||||
case 'pdf':
|
||||
exportPDF($statsData);
|
||||
break;
|
||||
case 'word':
|
||||
exportWord($statsData);
|
||||
break;
|
||||
default:
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
jsonResponse(['error' => 'Invalid action'], 400);
|
||||
}
|
||||
|
||||
function getStatsData($surveyId) {
|
||||
$db = getDB();
|
||||
|
||||
// Survey info
|
||||
$stmt = $db->prepare("SELECT s.*, u.full_name as creator_name FROM surveys s LEFT JOIN users u ON s.user_id = u.id WHERE s.id = ?");
|
||||
$stmt->execute([$surveyId]);
|
||||
$survey = $stmt->fetch();
|
||||
|
||||
// Total responses
|
||||
$stmt = $db->prepare("SELECT COUNT(*) as total FROM survey_responses WHERE survey_id = ?");
|
||||
$stmt->execute([$surveyId]);
|
||||
$totalResponses = (int)$stmt->fetch()['total'];
|
||||
|
||||
// Get sections with questions & stats
|
||||
$stmt = $db->prepare("SELECT * FROM survey_sections WHERE survey_id = ? ORDER BY section_number");
|
||||
$stmt->execute([$surveyId]);
|
||||
$sections = $stmt->fetchAll();
|
||||
|
||||
foreach ($sections as &$section) {
|
||||
// Load headings
|
||||
$hStmt = $db->prepare("SELECT * FROM survey_headings WHERE section_id = ? ORDER BY order_num");
|
||||
$hStmt->execute([$section['id']]);
|
||||
$headings = $hStmt->fetchAll();
|
||||
|
||||
$qStmt = $db->prepare("SELECT * FROM survey_questions WHERE section_id = ? ORDER BY order_num");
|
||||
$qStmt->execute([$section['id']]);
|
||||
$questions = $qStmt->fetchAll();
|
||||
|
||||
foreach ($questions as &$q) {
|
||||
if ($q['question_type'] === 'radio' && empty($q['options'])) {
|
||||
$aStmt = $db->prepare("
|
||||
SELECT answer_value, COUNT(*) as cnt
|
||||
FROM response_answers
|
||||
WHERE question_id = ? AND answer_value REGEXP '^[0-9]+$'
|
||||
GROUP BY answer_value
|
||||
");
|
||||
$aStmt->execute([$q['id']]);
|
||||
$freqData = $aStmt->fetchAll();
|
||||
|
||||
$values = [];
|
||||
$freq = [];
|
||||
for ($i = 1; $i <= 5; $i++) $freq[$i] = 0;
|
||||
|
||||
foreach ($freqData as $fd) {
|
||||
$val = (int)$fd['answer_value'];
|
||||
if ($val >= 1 && $val <= 5) {
|
||||
$freq[$val] = (int)$fd['cnt'];
|
||||
for ($j = 0; $j < (int)$fd['cnt']; $j++) $values[] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
$mean = count($values) > 0 ? array_sum($values) / count($values) : 0;
|
||||
$sd = 0;
|
||||
if (count($values) > 1) {
|
||||
$sumSq = 0;
|
||||
foreach ($values as $v) $sumSq += pow($v - $mean, 2);
|
||||
$sd = sqrt($sumSq / (count($values) - 1));
|
||||
}
|
||||
|
||||
$q['mean'] = round($mean, 2);
|
||||
$q['sd'] = round($sd, 2);
|
||||
$q['frequencies'] = $freq;
|
||||
$q['total_answers'] = count($values);
|
||||
|
||||
if ($mean >= 4.51) $q['interpretation'] = 'มากที่สุด';
|
||||
elseif ($mean >= 3.51) $q['interpretation'] = 'มาก';
|
||||
elseif ($mean >= 2.51) $q['interpretation'] = 'ปานกลาง';
|
||||
elseif ($mean >= 1.51) $q['interpretation'] = 'น้อย';
|
||||
else $q['interpretation'] = 'น้อยที่สุด';
|
||||
|
||||
} elseif (in_array($q['question_type'], ['dropdown', 'checkbox', 'radio'])) {
|
||||
$aStmt = $db->prepare("SELECT answer_value, COUNT(*) as cnt FROM response_answers WHERE question_id = ? GROUP BY answer_value ORDER BY cnt DESC");
|
||||
$aStmt->execute([$q['id']]);
|
||||
$q['frequencies'] = $aStmt->fetchAll();
|
||||
}
|
||||
|
||||
if ($q['question_type'] === 'text') {
|
||||
$aStmt = $db->prepare("SELECT answer_value FROM response_answers WHERE question_id = ? AND answer_value != ''");
|
||||
$aStmt->execute([$q['id']]);
|
||||
$q['text_responses'] = array_column($aStmt->fetchAll(), 'answer_value');
|
||||
}
|
||||
}
|
||||
unset($q); // Break the reference to prevent overwriting last element
|
||||
|
||||
// Group questions by heading
|
||||
$looseQuestions = [];
|
||||
$headingQuestionsMap = [];
|
||||
foreach ($questions as $q) {
|
||||
if (!empty($q['heading_id'])) {
|
||||
$headingQuestionsMap[$q['heading_id']][] = $q;
|
||||
} else {
|
||||
$looseQuestions[] = $q;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($headings as &$h) {
|
||||
$h['questions'] = $headingQuestionsMap[$h['id']] ?? [];
|
||||
}
|
||||
|
||||
$section['headings'] = $headings;
|
||||
$section['questions'] = $looseQuestions;
|
||||
}
|
||||
|
||||
return [
|
||||
'survey' => $survey,
|
||||
'total_responses' => $totalResponses,
|
||||
'sections' => $sections
|
||||
];
|
||||
}
|
||||
|
||||
function generateHTML($data) {
|
||||
$survey = $data['survey'];
|
||||
$totalResponses = $data['total_responses'];
|
||||
$sections = $data['sections'];
|
||||
|
||||
$html = '<!DOCTYPE html><html lang="th"><head><meta charset="UTF-8">';
|
||||
$html .= '<style>
|
||||
body { font-family: "TH Sarabun New", "Sarabun", sans-serif; font-size: 16px; line-height: 1.6; }
|
||||
h1 { text-align: center; font-size: 20px; margin-bottom: 5px; }
|
||||
h2 { font-size: 18px; margin-top: 20px; border-bottom: 1px solid #333; padding-bottom: 5px; }
|
||||
table { width: 100%; border-collapse: collapse; margin: 10px 0; }
|
||||
th, td { border: 1px solid #333; padding: 6px 10px; text-align: center; font-size: 14px; }
|
||||
th { background-color: #f0f0f0; font-weight: bold; }
|
||||
td:first-child { text-align: left; }
|
||||
.info { margin: 5px 0; }
|
||||
.text-response { padding: 5px 0; border-bottom: 1px dotted #ccc; }
|
||||
</style></head><body>';
|
||||
|
||||
$html .= '<h1>รายงานผลการประเมิน</h1>';
|
||||
$html .= '<h1>' . htmlspecialchars($survey['title']) . '</h1>';
|
||||
$html .= '<p class="info"><strong>ผู้สร้าง:</strong> ' . htmlspecialchars($survey['creator_name'] ?? '') . '</p>';
|
||||
$html .= '<p class="info"><strong>จำนวนผู้ตอบ:</strong> ' . $totalResponses . ' คน</p>';
|
||||
$html .= '<p class="info"><strong>วันที่:</strong> ' . date('d/m/Y', strtotime($survey['created_at'])) . '</p>';
|
||||
|
||||
foreach ($sections as $section) {
|
||||
$cleanSectionTitle = preg_replace('/^ส่วนที่\s*[\d๑-๙]+\s*/u', '', $section['title']);
|
||||
$cleanSectionTitle = preg_replace('/^\s*[\d๑-๙]+[\.\)\s\-:\x{00A0}]*\s*/u', '', $cleanSectionTitle);
|
||||
$html .= '<h2>' . htmlspecialchars($cleanSectionTitle) . '</h2>';
|
||||
|
||||
// Render headings and their questions
|
||||
if (!empty($section['headings'])) {
|
||||
foreach ($section['headings'] as $heading) {
|
||||
if (!empty($heading['questions'])) {
|
||||
$cleanHeadingTitle = preg_replace('/^\s*[\d๑-๙]+[\.\)\s\-:\x{00A0}]*\s*/u', '', $heading['title']);
|
||||
$html .= '<h3 style="font-size:16px; margin-top:15px; color:#2563eb; border-left:3px solid #2563eb; padding-left:8px;">' . htmlspecialchars($cleanHeadingTitle) . '</h3>';
|
||||
if (!empty($heading['description'])) {
|
||||
$html .= '<p style="font-size:13px; color:#666; margin:0 0 8px 11px;">' . htmlspecialchars($heading['description']) . '</p>';
|
||||
}
|
||||
$html .= renderQuestionsHTML($heading['questions']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Render loose questions
|
||||
if (!empty($section['questions'])) {
|
||||
$html .= renderQuestionsHTML($section['questions']);
|
||||
}
|
||||
}
|
||||
|
||||
$html .= '</body></html>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
function renderQuestionsHTML($questions) {
|
||||
$html = '';
|
||||
|
||||
$hasRating = false;
|
||||
foreach ($questions as $q) {
|
||||
if ($q['question_type'] === 'radio' && empty($q['options'])) { $hasRating = true; break; }
|
||||
}
|
||||
|
||||
if ($hasRating) {
|
||||
$html .= '<table>';
|
||||
$html .= '<tr><th rowspan="2">รายการประเมิน</th><th colspan="5">ระดับความคิดเห็น (ความถี่)</th><th rowspan="2">X̄</th><th rowspan="2">S.D.</th><th rowspan="2">แปลผล</th></tr>';
|
||||
$html .= '<tr><th>5</th><th>4</th><th>3</th><th>2</th><th>1</th></tr>';
|
||||
|
||||
$totalMean = 0;
|
||||
$totalSD = 0;
|
||||
$ratingCount = 0;
|
||||
|
||||
foreach ($questions as $q) {
|
||||
if ($q['question_type'] === 'radio' && empty($q['options'])) {
|
||||
$cleanText = preg_replace('/^\s*[\d๑-๙]+[\.\)\s\-:\x{00A0}]*\s*/u', '', $q['question_text']);
|
||||
$html .= '<tr>';
|
||||
$html .= '<td>' . htmlspecialchars($cleanText) . '</td>';
|
||||
for ($i = 5; $i >= 1; $i--) {
|
||||
$html .= '<td>' . ($q['frequencies'][$i] ?? 0) . '</td>';
|
||||
}
|
||||
$html .= '<td>' . number_format($q['mean'] ?? 0, 2) . '</td>';
|
||||
$html .= '<td>' . number_format($q['sd'] ?? 0, 2) . '</td>';
|
||||
$html .= '<td>' . ($q['interpretation'] ?? '-') . '</td>';
|
||||
$html .= '</tr>';
|
||||
|
||||
$totalMean += $q['mean'] ?? 0;
|
||||
$totalSD += $q['sd'] ?? 0;
|
||||
$ratingCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($ratingCount > 0) {
|
||||
$avgMean = $totalMean / $ratingCount;
|
||||
$avgSD = $totalSD / $ratingCount;
|
||||
$avgInterp = '';
|
||||
if ($avgMean >= 4.51) $avgInterp = 'มากที่สุด';
|
||||
elseif ($avgMean >= 3.51) $avgInterp = 'มาก';
|
||||
elseif ($avgMean >= 2.51) $avgInterp = 'ปานกลาง';
|
||||
elseif ($avgMean >= 1.51) $avgInterp = 'น้อย';
|
||||
else $avgInterp = 'น้อยที่สุด';
|
||||
|
||||
$html .= '<tr style="font-weight:bold;background:#f9f9f9;">';
|
||||
$html .= '<td>รวมเฉลี่ย</td><td colspan="5"></td>';
|
||||
$html .= '<td>' . number_format($avgMean, 2) . '</td>';
|
||||
$html .= '<td>' . number_format($avgSD, 2) . '</td>';
|
||||
$html .= '<td>' . $avgInterp . '</td>';
|
||||
$html .= '</tr>';
|
||||
}
|
||||
|
||||
$html .= '</table>';
|
||||
}
|
||||
|
||||
// Frequency tables for dropdown/checkbox/custom radio
|
||||
foreach ($questions as $q) {
|
||||
if (in_array($q['question_type'], ['dropdown', 'checkbox', 'radio']) && (!empty($q['options']) || $q['question_type'] !== 'radio')) {
|
||||
if (!empty($q['frequencies'])) {
|
||||
$cleanText = preg_replace('/^\s*[\d๑-๙]+[\.\)\s\-:\x{00A0}]*\s*/u', '', $q['question_text']);
|
||||
$html .= '<p><strong>' . htmlspecialchars($cleanText) . '</strong></p>';
|
||||
$html .= '<table><tr><th>ตัวเลือก</th><th>ความถี่</th><th>ร้อยละ</th></tr>';
|
||||
$totalQ = array_sum(array_column($q['frequencies'], 'cnt'));
|
||||
foreach ($q['frequencies'] as $f) {
|
||||
$percent = $totalQ > 0 ? round(($f['cnt'] / $totalQ) * 100, 1) : 0;
|
||||
$html .= '<tr><td>' . htmlspecialchars($f['answer_value']) . '</td><td>' . $f['cnt'] . '</td><td>' . $percent . '%</td></tr>';
|
||||
}
|
||||
$html .= '</table>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Text responses
|
||||
foreach ($questions as $q) {
|
||||
if ($q['question_type'] === 'text' && !empty($q['text_responses'])) {
|
||||
$cleanText = preg_replace('/^\s*[\d๑-๙]+[\.\)\s\-:\x{00A0}]*\s*/u', '', $q['question_text']);
|
||||
$html .= '<p><strong>' . htmlspecialchars($cleanText) . '</strong></p>';
|
||||
foreach ($q['text_responses'] as $i => $text) {
|
||||
$html .= '<div class="text-response">' . ($i+1) . '. ' . htmlspecialchars($text) . '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
function exportPDF($data) {
|
||||
$html = generateHTML($data);
|
||||
$title = $data['survey']['title'] ?? 'survey_report';
|
||||
$filename = 'report_' . preg_replace('/[^a-zA-Z0-9ก-๙]/u', '_', $title) . '.pdf';
|
||||
|
||||
// Use simple HTML-to-PDF approach
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
echo '<!DOCTYPE html><html><head><meta charset="UTF-8">';
|
||||
echo '<title>' . htmlspecialchars($title) . '</title>';
|
||||
echo '<style>@media print { body { -webkit-print-color-adjust: exact; } }</style>';
|
||||
echo '<script>window.onload = function() { window.print(); }</script>';
|
||||
echo '</head><body>';
|
||||
echo $html;
|
||||
echo '</body></html>';
|
||||
}
|
||||
|
||||
function exportWord($data) {
|
||||
$html = generateHTML($data);
|
||||
$title = $data['survey']['title'] ?? 'survey_report';
|
||||
$filename = 'report_' . preg_replace('/[^a-zA-Z0-9ก-๙]/u', '_', $title) . '.doc';
|
||||
|
||||
header('Content-Type: application/vnd.ms-word');
|
||||
header('Content-Disposition: attachment; filename="' . $filename . '"');
|
||||
header('Cache-Control: max-age=0');
|
||||
|
||||
echo '<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:w="urn:schemas-microsoft-com:office:word" xmlns="http://www.w3.org/TR/REC-html40">';
|
||||
echo '<head><meta charset="UTF-8"><title>' . htmlspecialchars($title) . '</title></head>';
|
||||
echo '<body>';
|
||||
echo $html;
|
||||
echo '</body></html>';
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
<?php
|
||||
/**
|
||||
* Import Google Form API
|
||||
* POST ?action=importFromUrl - Import survey structure from Google Form URL
|
||||
* POST ?action=importFromJson - Import survey structure from JSON
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
require_once __DIR__ . '/../config/auth.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
requireLogin();
|
||||
|
||||
$action = $_GET['action'] ?? '';
|
||||
|
||||
switch ($action) {
|
||||
case 'importFromUrl':
|
||||
importFromUrl();
|
||||
break;
|
||||
case 'importFromJson':
|
||||
importFromJson();
|
||||
break;
|
||||
default:
|
||||
jsonResponse(['error' => 'Invalid action'], 400);
|
||||
}
|
||||
|
||||
function importFromUrl() {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$url = trim($input['url'] ?? '');
|
||||
$projectId = !empty($input['project_id']) ? (int)$input['project_id'] : null;
|
||||
|
||||
if (empty($url)) {
|
||||
jsonResponse(['error' => 'กรุณาระบุ URL หรือ Link ของ Google Form'], 400);
|
||||
}
|
||||
|
||||
// Extract Form ID or viewform URL
|
||||
if (!preg_match('/docs\.google\.com\/forms\/d\/e?\/([^\/]+)/i', $url, $matches) && !preg_match('/docs\.google\.com\/forms\/d\/([^\/]+)/i', $url, $matches)) {
|
||||
jsonResponse(['error' => 'รูปแบบ URL ของ Google Form ไม่ถูกต้อง'], 400);
|
||||
}
|
||||
|
||||
// Attempt to fetch the public viewform HTML
|
||||
$fetchUrl = $url;
|
||||
if (strpos($fetchUrl, '/viewform') === false && strpos($fetchUrl, '/edit') === false) {
|
||||
$fetchUrl = rtrim($fetchUrl, '/') . '/viewform';
|
||||
}
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $fetchUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 15);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
$html = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode !== 200 || empty($html)) {
|
||||
jsonResponse(['error' => 'ไม่สามารถดึงข้อมูลจาก Google Form URL นี้ได้ โปรดตรวจสอบว่าเปิดเป็นสาธารณะ (Public) หรือไม่'], 400);
|
||||
}
|
||||
|
||||
// Parse FB_PUBLIC_LOAD_DATA_
|
||||
if (!preg_match('/FB_PUBLIC_LOAD_DATA_\s*=\s*(.+?);<\/script>/s', $html, $dataMatches)) {
|
||||
jsonResponse(['error' => 'ไม่พบโครงสร้างแบบสอบถามใน Google Form นี้'], 400);
|
||||
}
|
||||
|
||||
$formData = json_decode($dataMatches[1], true);
|
||||
if (!$formData || !isset($formData[1])) {
|
||||
jsonResponse(['error' => 'โครงสร้างข้อมูล Google Form ไม่ถูกต้อง'], 400);
|
||||
}
|
||||
|
||||
$parsedSurvey = parseGoogleFormData($formData);
|
||||
$surveyId = saveParsedSurveyToDB($parsedSurvey, $projectId);
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'message' => 'นำเข้าโครงสร้างแบบสอบถามจาก Google Form เรียบร้อยแล้ว',
|
||||
'survey_id' => $surveyId,
|
||||
'title' => $parsedSurvey['title']
|
||||
]);
|
||||
}
|
||||
|
||||
function importFromJson() {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$projectId = !empty($input['project_id']) ? (int)$input['project_id'] : null;
|
||||
$surveyData = $input['survey_data'] ?? null;
|
||||
|
||||
if (!$surveyData || empty($surveyData['title'])) {
|
||||
jsonResponse(['error' => 'ข้อมูล JSON ไม่ถูกต้อง หรือขาดชื่อแบบสอบถาม'], 400);
|
||||
}
|
||||
|
||||
$surveyId = saveParsedSurveyToDB($surveyData, $projectId);
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'message' => 'นำเข้าแบบสอบถามเรียบร้อยแล้ว',
|
||||
'survey_id' => $surveyId,
|
||||
'title' => $surveyData['title']
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: Parse Google Forms FB_PUBLIC_LOAD_DATA_ structure
|
||||
*/
|
||||
function parseGoogleFormData($data) {
|
||||
$title = $data[1][8] ?? $data[1][1] ?? $data[3] ?? 'แบบสอบถามจาก Google Forms';
|
||||
$description = $data[1][0] ?? '';
|
||||
|
||||
$rawItems = $data[1][1] ?? [];
|
||||
|
||||
$sections = [];
|
||||
$currentSection = [
|
||||
'section_number' => 1,
|
||||
'title' => 'ส่วนที่ 1 ข้อมูลทั่วไป',
|
||||
'description' => '',
|
||||
'category' => 'general',
|
||||
'questions' => []
|
||||
];
|
||||
|
||||
$sectionCount = 1;
|
||||
|
||||
foreach ($rawItems as $item) {
|
||||
if (!is_array($item)) continue;
|
||||
|
||||
$qTitle = $item[1] ?? '';
|
||||
$qDesc = $item[2] ?? '';
|
||||
$qTypeNum = $item[3] ?? 0;
|
||||
$qDetails = $item[4][0] ?? [];
|
||||
|
||||
if (empty($qTitle) && $qTypeNum != 8) continue; // Skip empty items except section breaks
|
||||
|
||||
// Type 8 = Section Break in Google Forms
|
||||
if ($qTypeNum == 8) {
|
||||
if (!empty($currentSection['questions'])) {
|
||||
$sections[] = $currentSection;
|
||||
}
|
||||
$sectionCount++;
|
||||
$currentSection = [
|
||||
'section_number' => $sectionCount,
|
||||
'title' => $qTitle ?: ('ส่วนที่ ' . $sectionCount),
|
||||
'description' => $qDesc,
|
||||
'category' => 'rating',
|
||||
'questions' => []
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
$qType = 'radio';
|
||||
$options = [];
|
||||
$scaleMin = 1;
|
||||
$scaleMax = 5;
|
||||
|
||||
// Map Google Forms Item Type numbers:
|
||||
// 0: Short Text, 1: Paragraph
|
||||
// 2: Radio (Multiple Choice), 3: Dropdown, 4: Checkbox
|
||||
// 5: Linear Scale (1-5)
|
||||
// 7: Grid / Matrix
|
||||
switch ($qTypeNum) {
|
||||
case 0:
|
||||
case 1:
|
||||
$qType = 'text';
|
||||
break;
|
||||
case 2:
|
||||
$qType = 'radio';
|
||||
if (isset($qDetails[1]) && is_array($qDetails[1])) {
|
||||
foreach ($qDetails[1] as $opt) {
|
||||
if (isset($opt[0])) $options[] = $opt[0];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
$qType = 'dropdown';
|
||||
if (isset($qDetails[1]) && is_array($qDetails[1])) {
|
||||
foreach ($qDetails[1] as $opt) {
|
||||
if (isset($opt[0])) $options[] = $opt[0];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
$qType = 'checkbox';
|
||||
if (isset($qDetails[1]) && is_array($qDetails[1])) {
|
||||
foreach ($qDetails[1] as $opt) {
|
||||
if (isset($opt[0])) $options[] = $opt[0];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
$qType = 'radio'; // Rating Scale
|
||||
$scaleMin = 1;
|
||||
$scaleMax = 5;
|
||||
break;
|
||||
default:
|
||||
$qType = 'text';
|
||||
break;
|
||||
}
|
||||
|
||||
$isRequired = isset($qDetails[2]) && $qDetails[2] == 1 ? 1 : 0;
|
||||
|
||||
$questionObj = [
|
||||
'question_text' => $qTitle,
|
||||
'question_type' => $qType,
|
||||
'is_required' => $isRequired,
|
||||
'description' => $qDesc,
|
||||
'options' => count($options) > 0 ? $options : null,
|
||||
'scale_min' => $scaleMin,
|
||||
'scale_max' => $scaleMax
|
||||
];
|
||||
|
||||
$currentSection['questions'][] = $questionObj;
|
||||
}
|
||||
|
||||
if (!empty($currentSection['questions'])) {
|
||||
$sections[] = $currentSection;
|
||||
}
|
||||
|
||||
if (empty($sections)) {
|
||||
$sections[] = [
|
||||
'section_number' => 1,
|
||||
'title' => 'ส่วนที่ 1 ข้อมูลและข้อคำถาม',
|
||||
'description' => 'นำเข้าจาก Google Forms',
|
||||
'category' => 'general',
|
||||
'questions' => []
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'title' => $title,
|
||||
'description' => $description ?: 'แบบสอบถามที่นำเข้าจาก Google Forms',
|
||||
'sections' => $sections
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Save parsed survey structure into database
|
||||
*/
|
||||
function saveParsedSurveyToDB($parsedSurvey, $projectId = null) {
|
||||
$db = getDB();
|
||||
$uniqueLink = bin2hex(random_bytes(16));
|
||||
|
||||
$db->beginTransaction();
|
||||
try {
|
||||
$stmt = $db->prepare("INSERT INTO surveys (user_id, project_id, title, description, unique_link, status) VALUES (?, ?, ?, ?, ?, 'active')");
|
||||
$stmt->execute([
|
||||
$_SESSION['user_id'],
|
||||
$projectId,
|
||||
$parsedSurvey['title'],
|
||||
$parsedSurvey['description'] ?? '',
|
||||
$uniqueLink
|
||||
]);
|
||||
$surveyId = $db->lastInsertId();
|
||||
|
||||
$sections = $parsedSurvey['sections'] ?? [];
|
||||
$secNum = 1;
|
||||
|
||||
foreach ($sections as $sec) {
|
||||
$stmt = $db->prepare("INSERT INTO survey_sections (survey_id, section_number, title, description, category) VALUES (?, ?, ?, ?, ?)");
|
||||
$stmt->execute([
|
||||
$surveyId,
|
||||
$sec['section_number'] ?? $secNum,
|
||||
$sec['title'] ?? ('ส่วนที่ ' . $secNum),
|
||||
$sec['description'] ?? '',
|
||||
$sec['category'] ?? 'rating'
|
||||
]);
|
||||
$sectionId = $db->lastInsertId();
|
||||
|
||||
$orderNum = 1;
|
||||
foreach ($sec['questions'] as $q) {
|
||||
$optionsJson = !empty($q['options']) ? json_encode($q['options'], JSON_UNESCAPED_UNICODE) : null;
|
||||
$stmtQ = $db->prepare("INSERT INTO survey_questions (section_id, question_text, question_type, is_required, order_num, options, scale_min, scale_max, description) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmtQ->execute([
|
||||
$sectionId,
|
||||
$q['question_text'],
|
||||
$q['question_type'] ?? 'radio',
|
||||
$q['is_required'] ?? 1,
|
||||
$orderNum++,
|
||||
$optionsJson,
|
||||
$q['scale_min'] ?? 1,
|
||||
$q['scale_max'] ?? 5,
|
||||
$q['description'] ?? null
|
||||
]);
|
||||
}
|
||||
$secNum++;
|
||||
}
|
||||
|
||||
$db->commit();
|
||||
return $surveyId;
|
||||
} catch (Exception $e) {
|
||||
$db->rollBack();
|
||||
jsonResponse(['error' => 'เกิดข้อผิดพลาดในการบันทึกแบบสอบถาม: ' . $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
/**
|
||||
* Import Responses API (from CSV / Excel exported from Google Forms)
|
||||
* POST ?survey_id=X
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
require_once __DIR__ . '/../config/auth.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
requireLogin();
|
||||
|
||||
$surveyId = (int)($_GET['survey_id'] ?? $_POST['survey_id'] ?? 0);
|
||||
|
||||
if (!$surveyId) {
|
||||
jsonResponse(['error' => 'Missing survey_id'], 400);
|
||||
}
|
||||
|
||||
$db = getDB();
|
||||
|
||||
// Verify access
|
||||
if (!isAdmin()) {
|
||||
$stmt = $db->prepare("SELECT user_id FROM surveys WHERE id = ?");
|
||||
$stmt->execute([$surveyId]);
|
||||
$s = $stmt->fetch();
|
||||
if (!$s || $s['user_id'] != $_SESSION['user_id']) {
|
||||
jsonResponse(['error' => 'ไม่มีสิทธิ์เข้าถึงแบบสอบถามนี้'], 403);
|
||||
}
|
||||
}
|
||||
|
||||
// Check uploaded file
|
||||
if (!isset($_FILES['csv_file']) || $_FILES['csv_file']['error'] !== UPLOAD_ERR_OK) {
|
||||
jsonResponse(['error' => 'กรุณาอัปโหลดไฟล์ CSV คำตอบจาก Google Forms'], 400);
|
||||
}
|
||||
|
||||
$filePath = $_FILES['csv_file']['tmp_name'];
|
||||
|
||||
// Read CSV
|
||||
$handle = fopen($filePath, 'r');
|
||||
if (!$handle) {
|
||||
jsonResponse(['error' => 'ไม่สามารถเปิดไฟล์ CSV ได้'], 500);
|
||||
}
|
||||
|
||||
// Check UTF-8 BOM or UTF-16
|
||||
$bom = fread($handle, 3);
|
||||
if ($bom !== "\xEF\xBB\xBF") {
|
||||
rewind($handle);
|
||||
}
|
||||
|
||||
// Get Headers (Row 1)
|
||||
$headers = fgetcsv($handle, 0, ",");
|
||||
if (!$headers) {
|
||||
fclose($handle);
|
||||
jsonResponse(['error' => 'ไฟล์ CSV ไม่มีข้อมูลหรือรูปแบบไม่ถูกต้อง'], 400);
|
||||
}
|
||||
|
||||
// Clean headers
|
||||
$cleanHeaders = array_map(function($h) {
|
||||
return trim(preg_replace('/\s+/', ' ', $h));
|
||||
}, $headers);
|
||||
|
||||
// Fetch all questions for this survey
|
||||
$stmt = $db->prepare("
|
||||
SELECT q.id, q.question_text, q.question_type
|
||||
FROM survey_questions q
|
||||
JOIN survey_sections s ON q.section_id = s.id
|
||||
WHERE s.survey_id = ?
|
||||
ORDER BY s.section_number, q.order_num
|
||||
");
|
||||
$stmt->execute([$surveyId]);
|
||||
$questions = $stmt->fetchAll();
|
||||
|
||||
if (empty($questions)) {
|
||||
fclose($handle);
|
||||
jsonResponse(['error' => 'ไม่พบข้อคำถามในแบบสอบถามนี้'], 400);
|
||||
}
|
||||
|
||||
// Map CSV Column Index -> question_id
|
||||
$colToQuestionMap = [];
|
||||
|
||||
function normalizeText($str) {
|
||||
$str = mb_strtolower($str, 'UTF-8');
|
||||
// Remove leading numbers or prefix like "1. ", "ข้อ 1. "
|
||||
$str = preg_replace('/^[\d๑-๙]+[\.\)\s\-:\x{00A0}]*/u', '', $str);
|
||||
return trim($str);
|
||||
}
|
||||
|
||||
foreach ($cleanHeaders as $colIdx => $headerText) {
|
||||
if (empty($headerText) || strtolower($headerText) === 'timestamp' || strtolower($headerText) === 'ประทับเวลา') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$normHeader = normalizeText($headerText);
|
||||
|
||||
// Try exact or normalized matching against survey questions
|
||||
foreach ($questions as $q) {
|
||||
$normQText = normalizeText($q['question_text']);
|
||||
|
||||
if ($normHeader === $normQText || strpos($normHeader, $normQText) !== false || strpos($normQText, $normHeader) !== false) {
|
||||
$colToQuestionMap[$colIdx] = $q['id'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: If column mapping count is low, map columns sequentially to questions
|
||||
if (count($colToQuestionMap) === 0) {
|
||||
$qIdx = 0;
|
||||
foreach ($cleanHeaders as $colIdx => $headerText) {
|
||||
if (strtolower($headerText) === 'timestamp' || strtolower($headerText) === 'ประทับเวลา') continue;
|
||||
if (isset($questions[$qIdx])) {
|
||||
$colToQuestionMap[$colIdx] = $questions[$qIdx]['id'];
|
||||
$qIdx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process Rows
|
||||
$importedCount = 0;
|
||||
|
||||
$db->beginTransaction();
|
||||
try {
|
||||
while (($row = fgetcsv($handle, 0, ",")) !== false) {
|
||||
if (empty(array_filter($row))) continue; // Skip empty lines
|
||||
|
||||
// Extract timestamp or use now
|
||||
$submittedAt = date('Y-m-d H:i:s');
|
||||
if (!empty($row[0])) {
|
||||
$ts = strtotime($row[0]);
|
||||
if ($ts !== false) {
|
||||
$submittedAt = date('Y-m-d H:i:s', $ts);
|
||||
}
|
||||
}
|
||||
|
||||
// Insert into survey_responses
|
||||
$stmtResp = $db->prepare("INSERT INTO survey_responses (survey_id, respondent_info, submitted_at) VALUES (?, ?, ?)");
|
||||
$stmtResp->execute([
|
||||
$surveyId,
|
||||
json_encode(['imported' => true, 'source' => 'google_forms_csv'], JSON_UNESCAPED_UNICODE),
|
||||
$submittedAt
|
||||
]);
|
||||
$responseId = $db->lastInsertId();
|
||||
|
||||
// Insert answers
|
||||
foreach ($colToQuestionMap as $colIdx => $qId) {
|
||||
$val = isset($row[$colIdx]) ? trim($row[$colIdx]) : '';
|
||||
if ($val !== '') {
|
||||
$stmtAns = $db->prepare("INSERT INTO response_answers (response_id, question_id, answer_value) VALUES (?, ?, ?)");
|
||||
$stmtAns->execute([$responseId, $qId, $val]);
|
||||
}
|
||||
}
|
||||
|
||||
$importedCount++;
|
||||
}
|
||||
|
||||
$db->commit();
|
||||
fclose($handle);
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'message' => "นำเข้าข้อมูลคำตอบสำเร็จจำนวน $importedCount รายการ",
|
||||
'imported_count' => $importedCount,
|
||||
'mapped_questions' => count($colToQuestionMap)
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
$db->rollBack();
|
||||
fclose($handle);
|
||||
jsonResponse(['error' => 'เกิดข้อผิดพลาดในการบันทึกคำตอบ: ' . $e->getMessage()], 500);
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/**
|
||||
* Mail Dispatch Helper using PHPMailer
|
||||
* TPU Survey System
|
||||
*/
|
||||
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use PHPMailer\PHPMailer\Exception;
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
require_once __DIR__ . '/../config/mail.php';
|
||||
|
||||
/**
|
||||
* Sends an email with the certificate attached
|
||||
*
|
||||
* @param string $toEmail Recipient email address
|
||||
* @param string $toName Recipient name
|
||||
* @param string $pdfPath Path to the generated PDF certificate
|
||||
* @param string $surveyTitle Survey title for context
|
||||
* @return bool True on success
|
||||
* @throws Exception on mail sending failure
|
||||
*/
|
||||
function sendCertificateEmail($toEmail, $toName, $pdfPath, $surveyTitle = '') {
|
||||
$mail = new PHPMailer(true);
|
||||
|
||||
try {
|
||||
// Server settings
|
||||
$mail->isSMTP();
|
||||
$mail->Host = SMTP_HOST;
|
||||
$mail->SMTPAuth = true;
|
||||
$mail->Username = SMTP_USER;
|
||||
$mail->Password = SMTP_PASS;
|
||||
$mail->SMTPSecure = SMTP_SECURE; // 'tls' or 'ssl'
|
||||
$mail->Port = SMTP_PORT;
|
||||
|
||||
// UTF-8 encoding for Thai support
|
||||
$mail->CharSet = 'UTF-8';
|
||||
|
||||
// Recipients
|
||||
$mail->setFrom(MAIL_FROM_EMAIL, MAIL_FROM_NAME);
|
||||
$mail->addAddress($toEmail, $toName);
|
||||
|
||||
// Attachments
|
||||
if (!file_exists($pdfPath)) {
|
||||
throw new Exception("ไม่พบไฟล์เกียรติบัตรที่จะแนบส่ง: " . $pdfPath);
|
||||
}
|
||||
|
||||
$attachmentName = 'เกียรติบัตร_' . str_replace(' ', '_', $toName) . '.pdf';
|
||||
$mail->addAttachment($pdfPath, $attachmentName);
|
||||
|
||||
// Content
|
||||
$mail->isHTML(true);
|
||||
$mail->Subject = 'เกียรติบัตรผ่านการอบรม/แบบประเมิน - ' . $surveyTitle;
|
||||
|
||||
$bodyHtml = "
|
||||
<div style='font-family: \"Sarabun\", sans-serif; line-height: 1.6; color: #333;'>
|
||||
<h2 style='color: #2563eb;'>สวัสดีคุณ {$toName},</h2>
|
||||
<p>ขอขอบคุณสำหรับความคิดเห็นและความร่วมมือในการตอบแบบประเมินโครงการ/หลักสูตร <strong>\"{$surveyTitle}\"</strong></p>
|
||||
<p>ทางผู้จัดงานขอมอบเกียรติบัตรผู้ผ่านการอบรมให้แก่ท่าน โดยท่านสามารถดาวน์โหลดไฟล์เกียรติบัตรที่แนบมากับอีเมลฉบับนี้ได้เลยครับ</p>
|
||||
<br>
|
||||
<p>ขอแสดงความนับถือ,</p>
|
||||
<p><strong>ฝ่ายจัดโครงการ มหาวิทยาลัยตาปี</strong></p>
|
||||
<hr style='border: 0; border-top: 1px solid #eee; margin: 20px 0;'>
|
||||
<p style='font-size: 0.8rem; color: #999;'>นี่เป็นอีเมลอัตโนมัติจากระบบ กรุณาอย่าตอบกลับอีเมลฉบับนี้</p>
|
||||
</div>
|
||||
";
|
||||
|
||||
$mail->Body = $bodyHtml;
|
||||
$mail->AltBody = "สวัสดีคุณ {$toName},\n\nขอขอบคุณสำหรับการตอบแบบประเมินโครงการ \"{$surveyTitle}\"\n\nทางผู้จัดงานขอมอบเกียรติบัตรผู้ผ่านการอบรมให้แก่ท่าน โดยไฟล์เกียรติบัตรถูกแนบมาพร้อมกับอีเมลนี้เรียบร้อยแล้ว\n\nขอแสดงความนับถือ,\nฝ่ายจัดโครงการ มหาวิทยาลัยตาปี";
|
||||
|
||||
$mail->send();
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
throw new Exception("ระบบส่งอีเมลขัดข้อง: " . $mail->ErrorInfo);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
/**
|
||||
* Profile API — any logged-in user can view/update their own profile
|
||||
* GET ?action=get — Get current user's profile
|
||||
* POST ?action=update — Update current user's profile (except personnel_code)
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
require_once __DIR__ . '/../config/auth.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
requireLogin();
|
||||
|
||||
$action = $_GET['action'] ?? '';
|
||||
|
||||
switch ($action) {
|
||||
case 'get':
|
||||
getProfile();
|
||||
break;
|
||||
case 'update':
|
||||
updateProfile();
|
||||
break;
|
||||
default:
|
||||
jsonResponse(['error' => 'Invalid action'], 400);
|
||||
}
|
||||
|
||||
function getProfile() {
|
||||
$userId = $_SESSION['user_id'];
|
||||
$db = getDB();
|
||||
$stmt = $db->prepare("SELECT id, username, role, full_name, personnel_code, subject_area, branch_code, email, created_at FROM users WHERE id = ?");
|
||||
$stmt->execute([$userId]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if (!$user) {
|
||||
jsonResponse(['error' => 'ไม่พบข้อมูลผู้ใช้'], 404);
|
||||
}
|
||||
jsonResponse(['user' => $user]);
|
||||
}
|
||||
|
||||
function updateProfile() {
|
||||
$userId = $_SESSION['user_id'];
|
||||
$input = getInput();
|
||||
|
||||
$fullName = trim($input['full_name'] ?? '');
|
||||
$subjectArea = trim($input['subject_area'] ?? '');
|
||||
$branchCode = trim($input['branch_code'] ?? '');
|
||||
$email = trim($input['email'] ?? '');
|
||||
|
||||
$db = getDB();
|
||||
|
||||
$fields = [];
|
||||
$params = [];
|
||||
|
||||
if (!empty($fullName)) { $fields[] = "full_name = ?"; $params[] = $fullName; }
|
||||
if ($subjectArea !== '') { $fields[] = "subject_area = ?"; $params[] = $subjectArea; }
|
||||
if ($branchCode !== '') { $fields[] = "branch_code = ?"; $params[] = $branchCode; }
|
||||
if (isset($input['email'])) { $fields[] = "email = ?"; $params[] = trim($input['email']); }
|
||||
|
||||
// Update password if provided
|
||||
if (!empty($input['password'])) {
|
||||
if (strlen($input['password']) < 4) {
|
||||
jsonResponse(['error' => 'รหัสผ่านต้องมีอย่างน้อย 4 ตัวอักษร'], 400);
|
||||
}
|
||||
$fields[] = "password_hash = ?";
|
||||
$params[] = password_hash($input['password'], PASSWORD_DEFAULT);
|
||||
}
|
||||
|
||||
if (empty($fields)) {
|
||||
jsonResponse(['error' => 'ไม่มีข้อมูลที่ต้องอัปเดต'], 400);
|
||||
}
|
||||
|
||||
$params[] = $userId;
|
||||
$sql = "UPDATE users SET " . implode(', ', $fields) . " WHERE id = ?";
|
||||
$stmt = $db->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
|
||||
// Update session name if changed
|
||||
if (!empty($fullName)) {
|
||||
$_SESSION['full_name'] = $fullName;
|
||||
}
|
||||
|
||||
jsonResponse(['success' => true, 'message' => 'อัปเดตข้อมูลส่วนตัวสำเร็จ']);
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
<?php
|
||||
/**
|
||||
* Survey Responses API
|
||||
* POST ?action=submit — Submit response (public)
|
||||
* GET ?action=list&survey_id=X — List responses for a survey
|
||||
* GET ?action=get&id=X — Get single response detail
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
require_once __DIR__ . '/../config/auth.php';
|
||||
require_once __DIR__ . '/../config/mail.php';
|
||||
require_once __DIR__ . '/certificate_helper.php';
|
||||
require_once __DIR__ . '/mail_helper.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$action = $_GET['action'] ?? '';
|
||||
|
||||
// Public endpoint
|
||||
if ($action === 'submit') {
|
||||
submitResponse();
|
||||
exit;
|
||||
}
|
||||
|
||||
requireLogin();
|
||||
|
||||
switch ($action) {
|
||||
case 'list':
|
||||
listResponses();
|
||||
break;
|
||||
case 'get':
|
||||
getResponse();
|
||||
break;
|
||||
default:
|
||||
jsonResponse(['error' => 'Invalid action'], 400);
|
||||
}
|
||||
|
||||
function submitResponse() {
|
||||
$input = getInput();
|
||||
$surveyLink = $input['survey_link'] ?? '';
|
||||
$answers = $input['answers'] ?? [];
|
||||
$respondentInfo = $input['respondent_info'] ?? [];
|
||||
|
||||
if (empty($surveyLink)) {
|
||||
jsonResponse(['error' => 'Missing survey link'], 400);
|
||||
}
|
||||
|
||||
$db = getDB();
|
||||
|
||||
// Find survey
|
||||
$stmt = $db->prepare("SELECT id, status, title, has_certificate, certificate_template, cert_name_y, cert_code_y FROM surveys WHERE unique_link = ?");
|
||||
$stmt->execute([$surveyLink]);
|
||||
$survey = $stmt->fetch();
|
||||
|
||||
if (!$survey) jsonResponse(['error' => 'ไม่พบแบบสอบถาม'], 404);
|
||||
if ($survey['status'] !== 'active') jsonResponse(['error' => 'แบบสอบถามนี้ปิดรับคำตอบแล้ว'], 400);
|
||||
|
||||
// ตรวจสอบข้อมูลเพื่อรับเกียรติบัตรล่วงหน้า (และทำ Fallback ตรวจจับจากคำตอบ)
|
||||
if (!empty($survey['has_certificate'])) {
|
||||
$email = trim($respondentInfo['email'] ?? '');
|
||||
$name = trim($respondentInfo['name'] ?? '');
|
||||
|
||||
// Fallback: หากส่งค่าจาก JS เป็นค่าว่าง (เช่น กรณีเบราว์เซอร์แคชไฟล์เก่า) ค้นหาอีเมลจากคำตอบ
|
||||
if (empty($email) || empty($name)) {
|
||||
foreach ($answers as $ans) {
|
||||
$val = trim($ans['value'] ?? '');
|
||||
if (filter_var($val, FILTER_VALIDATE_EMAIL) &&
|
||||
(str_ends_with($val, '@tapee.ac.th') || str_ends_with($val, '@student.tapee.ac.th') || str_ends_with($val, '@gmail.com') || str_ends_with($val, '@gmil.com'))) {
|
||||
$email = $val;
|
||||
}
|
||||
}
|
||||
if (!empty($email) && empty($name)) {
|
||||
// ค้นหาช่องคำตอบชื่อจริง
|
||||
$stmtQ = $db->prepare("
|
||||
SELECT q.id
|
||||
FROM survey_questions q
|
||||
JOIN survey_sections s ON q.section_id = s.id
|
||||
WHERE s.survey_id = ? AND (q.question_text LIKE '%ชื่อ%' OR q.question_text LIKE '%นามสกุล%')
|
||||
LIMIT 1
|
||||
");
|
||||
$stmtQ->execute([$survey['id']]);
|
||||
$qRow = $stmtQ->fetch();
|
||||
if ($qRow) {
|
||||
$nameQuestionId = $qRow['id'];
|
||||
foreach ($answers as $ans) {
|
||||
if (($ans['question_id'] ?? 0) == $nameQuestionId) {
|
||||
$name = trim($ans['value'] ?? '');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// อัปเดตกลับเข้าตัวแปร respondentInfo เพื่อเตรียมบันทึกลง JSON
|
||||
$respondentInfo['email'] = $email;
|
||||
$respondentInfo['name'] = $name;
|
||||
}
|
||||
|
||||
// ตรวจสอบการกรอกอีเมลซ้ำสำหรับแบบสอบถามที่แจกเกียรติบัตร (1 อีเมล ต่อ 1 สิทธิ์)
|
||||
if (!empty($email)) {
|
||||
$stmtCheck = $db->prepare("
|
||||
SELECT COUNT(*) FROM survey_responses
|
||||
WHERE survey_id = ? AND LOWER(JSON_UNQUOTE(JSON_EXTRACT(respondent_info, '$.email'))) = ?
|
||||
");
|
||||
$stmtCheck->execute([$survey['id'], strtolower($email)]);
|
||||
if ($stmtCheck->fetchColumn() > 0) {
|
||||
jsonResponse(['error' => 'อีเมลนี้เคยทำแบบประเมินและขอรับเกียรติบัตรไปแล้ว (สามารถลงทะเบียนได้เพียง 1 ครั้งต่อ 1 อีเมลเท่านั้น)'], 400);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$db->beginTransaction();
|
||||
try {
|
||||
$stmt = $db->prepare("INSERT INTO survey_responses (survey_id, respondent_info, ip_address) VALUES (?, ?, ?)");
|
||||
$stmt->execute([
|
||||
$survey['id'],
|
||||
json_encode($respondentInfo, JSON_UNESCAPED_UNICODE),
|
||||
$_SERVER['REMOTE_ADDR'] ?? ''
|
||||
]);
|
||||
$responseId = $db->lastInsertId();
|
||||
|
||||
foreach ($answers as $answer) {
|
||||
$questionId = (int)($answer['question_id'] ?? 0);
|
||||
$value = $answer['value'] ?? '';
|
||||
|
||||
if ($questionId) {
|
||||
$stmt2 = $db->prepare("INSERT INTO response_answers (response_id, question_id, answer_value) VALUES (?, ?, ?)");
|
||||
$stmt2->execute([$responseId, $questionId, is_array($value) ? json_encode($value, JSON_UNESCAPED_UNICODE) : $value]);
|
||||
}
|
||||
}
|
||||
|
||||
$db->commit();
|
||||
|
||||
// ดำเนินการสร้างและจัดส่งเกียรติบัตรอัตโนมัติหากเปิดใช้งานเกียรติบัตรสำหรับแบบสอบถามนี้
|
||||
if (!empty($survey['has_certificate'])) {
|
||||
$email = $respondentInfo['email'] ?? '';
|
||||
$name = $respondentInfo['name'] ?? '';
|
||||
|
||||
if (!empty($email) && !empty($name)) {
|
||||
// ดึงข้อมูลหัวข้อแบบสอบถามเพื่อใช้เป็นชื่อหัวข้ออีเมล
|
||||
$surveyTitle = $survey['title'] ?? 'โครงการอบรม';
|
||||
|
||||
// สร้างรหัสตรวจสอบเกียรติบัตรที่ไม่ซ้ำกัน (เช่น TPU-2569-0016)
|
||||
$thaiYear = date('Y') + 543;
|
||||
$certCode = 'TPU-' . $thaiYear . '-' . str_pad($responseId, 4, '0', STR_PAD_LEFT);
|
||||
|
||||
// อัปเดตรหัสตรวจสอบลงในฐานข้อมูลตาราง survey_responses
|
||||
$stmtUpdateCode = $db->prepare("UPDATE survey_responses SET cert_code = ? WHERE id = ?");
|
||||
$stmtUpdateCode->execute([$certCode, $responseId]);
|
||||
|
||||
// สร้างโฟลเดอร์ชั่วคราวสำหรับเกียรติบัตร (ถ้ายังไม่มี)
|
||||
$tempDir = __DIR__ . '/../temp_certificates';
|
||||
if (!is_dir($tempDir)) {
|
||||
mkdir($tempDir, 0777, true);
|
||||
}
|
||||
chmod($tempDir, 0777);
|
||||
|
||||
$pdfFileName = 'cert_' . $responseId . '_' . time() . '.pdf';
|
||||
$pdfPath = $tempDir . '/' . $pdfFileName;
|
||||
|
||||
try {
|
||||
// 1. สร้างเกียรติบัตร PDF โดยใช้พิกัดและเทมเพลตที่ตั้งค่าเฉพาะของแบบสอบถามนี้
|
||||
$yPos = isset($survey['cert_name_y']) ? (double)$survey['cert_name_y'] : 82.5;
|
||||
$codeY = isset($survey['cert_code_y']) ? (double)$survey['cert_code_y'] : 190.5;
|
||||
$bgImage = $survey['certificate_template'] ?? '';
|
||||
generateCertificate($name, $pdfPath, $yPos, $certCode, $bgImage, $codeY);
|
||||
|
||||
// 2. ส่งอีเมลพร้อมแนบเกียรติบัตร
|
||||
sendCertificateEmail($email, $name, $pdfPath, $surveyTitle);
|
||||
|
||||
// 3. ลบไฟล์ PDF ชั่วคราวหลังจากส่งสำเร็จ
|
||||
if (file_exists($pdfPath)) {
|
||||
unlink($pdfPath);
|
||||
}
|
||||
} catch (Exception $mailEx) {
|
||||
// บันทึกข้อผิดพลาดกรณีส่งเมลไม่สำเร็จ แต่ไม่ทำให้การส่งแบบสอบถามทั้งหมดล้มเหลว
|
||||
// เพื่อให้ผู้ใช้ตอบแบบสอบถามเสร็จสมบูรณ์
|
||||
error_log("Certificate dispatch failed for response ID " . $responseId . ": " . $mailEx->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jsonResponse(['success' => true, 'message' => 'ส่งแบบสอบถามสำเร็จ ขอบคุณค่ะ/ครับ'], 201);
|
||||
} catch (Exception $e) {
|
||||
$db->rollBack();
|
||||
jsonResponse(['error' => 'เกิดข้อผิดพลาด: ' . $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
function listResponses() {
|
||||
$surveyId = (int)($_GET['survey_id'] ?? 0);
|
||||
if (!$surveyId) jsonResponse(['error' => 'Missing survey_id'], 400);
|
||||
|
||||
$db = getDB();
|
||||
|
||||
// Check access
|
||||
if (!isAdmin()) {
|
||||
$stmt = $db->prepare("SELECT user_id FROM surveys WHERE id = ?");
|
||||
$stmt->execute([$surveyId]);
|
||||
$survey = $stmt->fetch();
|
||||
if (!$survey || $survey['user_id'] != $_SESSION['user_id']) {
|
||||
jsonResponse(['error' => 'ไม่มีสิทธิ์'], 403);
|
||||
}
|
||||
}
|
||||
|
||||
$stmt = $db->prepare("
|
||||
SELECT id, respondent_info, ip_address, submitted_at
|
||||
FROM survey_responses
|
||||
WHERE survey_id = ?
|
||||
ORDER BY submitted_at DESC
|
||||
");
|
||||
$stmt->execute([$surveyId]);
|
||||
$responses = $stmt->fetchAll();
|
||||
|
||||
foreach ($responses as &$r) {
|
||||
$r['respondent_info'] = json_decode($r['respondent_info'], true);
|
||||
}
|
||||
|
||||
jsonResponse(['responses' => $responses, 'total' => count($responses)]);
|
||||
}
|
||||
|
||||
function getResponse() {
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
if (!$id) jsonResponse(['error' => 'Missing ID'], 400);
|
||||
|
||||
$db = getDB();
|
||||
$stmt = $db->prepare("
|
||||
SELECT sr.*, s.title as survey_title, s.user_id
|
||||
FROM survey_responses sr
|
||||
JOIN surveys s ON sr.survey_id = s.id
|
||||
WHERE sr.id = ?
|
||||
");
|
||||
$stmt->execute([$id]);
|
||||
$response = $stmt->fetch();
|
||||
|
||||
if (!$response) jsonResponse(['error' => 'ไม่พบคำตอบ'], 404);
|
||||
if (!isAdmin() && $response['user_id'] != $_SESSION['user_id']) {
|
||||
jsonResponse(['error' => 'ไม่มีสิทธิ์'], 403);
|
||||
}
|
||||
|
||||
$response['respondent_info'] = json_decode($response['respondent_info'], true);
|
||||
|
||||
// Get answers
|
||||
$stmt = $db->prepare("
|
||||
SELECT ra.*, sq.question_text, sq.question_type
|
||||
FROM response_answers ra
|
||||
JOIN survey_questions sq ON ra.question_id = sq.id
|
||||
WHERE ra.response_id = ?
|
||||
ORDER BY sq.order_num
|
||||
");
|
||||
$stmt->execute([$id]);
|
||||
$response['answers'] = $stmt->fetchAll();
|
||||
|
||||
jsonResponse(['response' => $response]);
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
<?php
|
||||
/**
|
||||
* Statistics API
|
||||
* GET ?action=summary&survey_id=X — Summary statistics
|
||||
* GET ?action=detail&survey_id=X — Detailed per-question stats
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
require_once __DIR__ . '/../config/auth.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
requireLogin();
|
||||
|
||||
$action = $_GET['action'] ?? 'summary';
|
||||
$surveyId = (int)($_GET['survey_id'] ?? 0);
|
||||
|
||||
if (!$surveyId) jsonResponse(['error' => 'Missing survey_id'], 400);
|
||||
|
||||
// Check access
|
||||
$db = getDB();
|
||||
if (!isAdmin()) {
|
||||
$stmt = $db->prepare("SELECT user_id FROM surveys WHERE id = ?");
|
||||
$stmt->execute([$surveyId]);
|
||||
$survey = $stmt->fetch();
|
||||
if (!$survey || $survey['user_id'] != $_SESSION['user_id']) {
|
||||
jsonResponse(['error' => 'ไม่มีสิทธิ์'], 403);
|
||||
}
|
||||
}
|
||||
|
||||
switch ($action) {
|
||||
case 'summary':
|
||||
getSummary($surveyId);
|
||||
break;
|
||||
case 'detail':
|
||||
getDetail($surveyId);
|
||||
break;
|
||||
default:
|
||||
jsonResponse(['error' => 'Invalid action'], 400);
|
||||
}
|
||||
|
||||
function getSummary($surveyId) {
|
||||
$db = getDB();
|
||||
|
||||
// Total responses
|
||||
$stmt = $db->prepare("SELECT COUNT(*) as total FROM survey_responses WHERE survey_id = ?");
|
||||
$stmt->execute([$surveyId]);
|
||||
$total = $stmt->fetch()['total'];
|
||||
|
||||
// Get survey info
|
||||
$stmt = $db->prepare("SELECT title, status, created_at FROM surveys WHERE id = ?");
|
||||
$stmt->execute([$surveyId]);
|
||||
$surveyInfo = $stmt->fetch();
|
||||
|
||||
// Get overall mean and SD for radio/rating questions
|
||||
$stmt = $db->prepare("
|
||||
SELECT
|
||||
AVG(CAST(ra.answer_value AS DECIMAL(10,2))) as overall_mean,
|
||||
STDDEV(CAST(ra.answer_value AS DECIMAL(10,2))) as overall_sd
|
||||
FROM response_answers ra
|
||||
JOIN survey_questions sq ON ra.question_id = sq.id
|
||||
JOIN survey_sections ss ON sq.section_id = ss.id
|
||||
WHERE ss.survey_id = ? AND sq.question_type = 'radio' AND sq.options IS NULL AND ra.answer_value REGEXP '^[0-9]+$'
|
||||
");
|
||||
$stmt->execute([$surveyId]);
|
||||
$overall = $stmt->fetch();
|
||||
|
||||
jsonResponse([
|
||||
'survey' => $surveyInfo,
|
||||
'total_responses' => (int)$total,
|
||||
'overall_mean' => $overall['overall_mean'] ? round((float)$overall['overall_mean'], 2) : 0,
|
||||
'overall_sd' => $overall['overall_sd'] ? round((float)$overall['overall_sd'], 2) : 0
|
||||
]);
|
||||
}
|
||||
|
||||
function getDetail($surveyId) {
|
||||
$db = getDB();
|
||||
|
||||
// Total responses
|
||||
$stmt = $db->prepare("SELECT COUNT(*) as total FROM survey_responses WHERE survey_id = ?");
|
||||
$stmt->execute([$surveyId]);
|
||||
$totalResponses = (int)$stmt->fetch()['total'];
|
||||
|
||||
// Get sections with questions
|
||||
$stmt = $db->prepare("SELECT * FROM survey_sections WHERE survey_id = ? ORDER BY section_number");
|
||||
$stmt->execute([$surveyId]);
|
||||
$sections = $stmt->fetchAll();
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($sections as $section) {
|
||||
$qStmt = $db->prepare("SELECT * FROM survey_questions WHERE section_id = ? ORDER BY order_num");
|
||||
$qStmt->execute([$section['id']]);
|
||||
$questions = $qStmt->fetchAll();
|
||||
|
||||
$sectionStats = [
|
||||
'section_number' => $section['section_number'],
|
||||
'title' => $section['title'],
|
||||
'headings' => [],
|
||||
'questions' => []
|
||||
];
|
||||
|
||||
// Load headings for this section
|
||||
$headingStmt = $db->prepare("SELECT * FROM survey_headings WHERE section_id = ? ORDER BY order_num");
|
||||
$headingStmt->execute([$section['id']]);
|
||||
$headings = $headingStmt->fetchAll();
|
||||
|
||||
foreach ($questions as $q) {
|
||||
$qStat = [
|
||||
'id' => $q['id'],
|
||||
'heading_id' => $q['heading_id'] ?? null,
|
||||
'question_text' => $q['question_text'],
|
||||
'question_type' => $q['question_type']
|
||||
];
|
||||
|
||||
if ($q['question_type'] === 'radio' && empty($q['options'])) {
|
||||
// Calculate mean, SD, frequency for rating questions (1-5)
|
||||
$aStmt = $db->prepare("
|
||||
SELECT answer_value, COUNT(*) as cnt
|
||||
FROM response_answers
|
||||
WHERE question_id = ? AND answer_value REGEXP '^[0-9]+$'
|
||||
GROUP BY answer_value
|
||||
ORDER BY answer_value
|
||||
");
|
||||
$aStmt->execute([$q['id']]);
|
||||
$freqData = $aStmt->fetchAll();
|
||||
|
||||
$frequencies = [];
|
||||
$values = [];
|
||||
$totalAnswers = 0;
|
||||
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
$frequencies[$i] = ['value' => $i, 'count' => 0, 'percentage' => 0];
|
||||
}
|
||||
|
||||
foreach ($freqData as $fd) {
|
||||
$val = (int)$fd['answer_value'];
|
||||
if ($val >= 1 && $val <= 5) {
|
||||
$frequencies[$val]['count'] = (int)$fd['cnt'];
|
||||
$totalAnswers += (int)$fd['cnt'];
|
||||
for ($j = 0; $j < (int)$fd['cnt']; $j++) {
|
||||
$values[] = $val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate percentages
|
||||
foreach ($frequencies as &$f) {
|
||||
$f['percentage'] = $totalAnswers > 0 ? round(($f['count'] / $totalAnswers) * 100, 1) : 0;
|
||||
}
|
||||
unset($f);
|
||||
|
||||
// Calculate mean
|
||||
$mean = count($values) > 0 ? array_sum($values) / count($values) : 0;
|
||||
|
||||
// Calculate SD
|
||||
$sd = 0;
|
||||
if (count($values) > 1) {
|
||||
$sumSquaredDiff = 0;
|
||||
foreach ($values as $v) {
|
||||
$sumSquaredDiff += pow($v - $mean, 2);
|
||||
}
|
||||
$sd = sqrt($sumSquaredDiff / (count($values) - 1));
|
||||
}
|
||||
|
||||
// Interpretation
|
||||
$interpretation = '';
|
||||
if ($mean >= 4.51) $interpretation = 'มากที่สุด';
|
||||
elseif ($mean >= 3.51) $interpretation = 'มาก';
|
||||
elseif ($mean >= 2.51) $interpretation = 'ปานกลาง';
|
||||
elseif ($mean >= 1.51) $interpretation = 'น้อย';
|
||||
else $interpretation = 'น้อยที่สุด';
|
||||
|
||||
$qStat['mean'] = round($mean, 2);
|
||||
$qStat['sd'] = round($sd, 2);
|
||||
$qStat['interpretation'] = $interpretation;
|
||||
$qStat['frequencies'] = array_values($frequencies);
|
||||
$qStat['total_answers'] = $totalAnswers;
|
||||
|
||||
} elseif ($q['question_type'] === 'text') {
|
||||
// Collect text responses
|
||||
$aStmt = $db->prepare("
|
||||
SELECT answer_value FROM response_answers
|
||||
WHERE question_id = ? AND answer_value != ''
|
||||
ORDER BY id DESC
|
||||
");
|
||||
$aStmt->execute([$q['id']]);
|
||||
$qStat['text_responses'] = array_column($aStmt->fetchAll(), 'answer_value');
|
||||
$qStat['total_answers'] = count($qStat['text_responses']);
|
||||
|
||||
} elseif (in_array($q['question_type'], ['dropdown', 'checkbox', 'radio'])) {
|
||||
// Frequency for dropdown/checkbox/custom radio
|
||||
$aStmt = $db->prepare("
|
||||
SELECT answer_value, COUNT(*) as cnt
|
||||
FROM response_answers
|
||||
WHERE question_id = ?
|
||||
GROUP BY answer_value
|
||||
ORDER BY cnt DESC
|
||||
");
|
||||
$aStmt->execute([$q['id']]);
|
||||
$freqData = $aStmt->fetchAll();
|
||||
|
||||
$totalAnswers = array_sum(array_column($freqData, 'cnt'));
|
||||
$qStat['frequencies'] = array_map(function($fd) use ($totalAnswers) {
|
||||
return [
|
||||
'value' => $fd['answer_value'],
|
||||
'count' => (int)$fd['cnt'],
|
||||
'percentage' => $totalAnswers > 0 ? round(((int)$fd['cnt'] / $totalAnswers) * 100, 1) : 0
|
||||
];
|
||||
}, $freqData);
|
||||
$qStat['total_answers'] = $totalAnswers;
|
||||
}
|
||||
|
||||
$sectionStats['questions'][] = $qStat;
|
||||
}
|
||||
|
||||
// Build heading stats groups
|
||||
$headingStats = [];
|
||||
foreach ($headings as $h) {
|
||||
$hStat = [
|
||||
'id' => $h['id'],
|
||||
'title' => $h['title'],
|
||||
'description' => $h['description'],
|
||||
'questions' => []
|
||||
];
|
||||
foreach ($sectionStats['questions'] as $qs) {
|
||||
if (($qs['heading_id'] ?? null) == $h['id']) {
|
||||
$hStat['questions'][] = $qs;
|
||||
}
|
||||
}
|
||||
$headingStats[] = $hStat;
|
||||
}
|
||||
$sectionStats['headings'] = $headingStats;
|
||||
|
||||
// Filter loose questions (no heading)
|
||||
$sectionStats['questions'] = array_values(array_filter($sectionStats['questions'], function($qs) {
|
||||
return empty($qs['heading_id']);
|
||||
}));
|
||||
|
||||
$result[] = $sectionStats;
|
||||
}
|
||||
|
||||
jsonResponse([
|
||||
'total_responses' => $totalResponses,
|
||||
'sections' => $result
|
||||
]);
|
||||
}
|
||||
+533
@@ -0,0 +1,533 @@
|
||||
<?php
|
||||
/**
|
||||
* Surveys CRUD API
|
||||
* GET ?action=list — List surveys (filtered by role)
|
||||
* GET ?action=get&id=X — Get survey with sections & questions
|
||||
* GET ?action=getByLink&link=X — Get survey by unique link (public)
|
||||
* POST ?action=create — Create survey with sections & questions
|
||||
* POST ?action=update — Update survey
|
||||
* POST ?action=delete — Delete survey
|
||||
* POST ?action=generateLink — Generate new unique link
|
||||
* POST ?action=updateStatus — Update survey status
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
require_once __DIR__ . '/../config/auth.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$action = $_GET['action'] ?? '';
|
||||
|
||||
// Public endpoint for survey respondents
|
||||
if ($action === 'getByLink') {
|
||||
getByLink();
|
||||
exit;
|
||||
}
|
||||
|
||||
requireLogin();
|
||||
|
||||
switch ($action) {
|
||||
case 'list':
|
||||
listSurveys();
|
||||
break;
|
||||
case 'get':
|
||||
getSurvey();
|
||||
break;
|
||||
case 'create':
|
||||
createSurvey();
|
||||
break;
|
||||
case 'update':
|
||||
updateSurvey();
|
||||
break;
|
||||
case 'delete':
|
||||
deleteSurvey();
|
||||
break;
|
||||
case 'generateLink':
|
||||
generateLink();
|
||||
break;
|
||||
case 'updateStatus':
|
||||
updateStatus();
|
||||
break;
|
||||
case 'duplicate':
|
||||
duplicateSurvey();
|
||||
break;
|
||||
case 'uploadTemplate':
|
||||
uploadTemplate();
|
||||
break;
|
||||
default:
|
||||
jsonResponse(['error' => 'Invalid action'], 400);
|
||||
}
|
||||
|
||||
function listSurveys() {
|
||||
$db = getDB();
|
||||
|
||||
if (isAdmin()) {
|
||||
$stmt = $db->query("
|
||||
SELECT s.*, u.full_name as creator_name,
|
||||
(SELECT COUNT(*) FROM survey_responses WHERE survey_id = s.id) as response_count
|
||||
FROM surveys s
|
||||
LEFT JOIN users u ON s.user_id = u.id
|
||||
ORDER BY s.created_at DESC
|
||||
");
|
||||
} else {
|
||||
$stmt = $db->prepare("
|
||||
SELECT s.*, u.full_name as creator_name,
|
||||
(SELECT COUNT(*) FROM survey_responses WHERE survey_id = s.id) as response_count
|
||||
FROM surveys s
|
||||
LEFT JOIN users u ON s.user_id = u.id
|
||||
WHERE s.user_id = ?
|
||||
ORDER BY s.created_at DESC
|
||||
");
|
||||
$stmt->execute([$_SESSION['user_id']]);
|
||||
}
|
||||
|
||||
jsonResponse(['surveys' => $stmt->fetchAll()]);
|
||||
}
|
||||
|
||||
function getSurvey() {
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
if (!$id) jsonResponse(['error' => 'Missing ID'], 400);
|
||||
|
||||
$db = getDB();
|
||||
$stmt = $db->prepare("SELECT * FROM surveys WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$survey = $stmt->fetch();
|
||||
|
||||
if (!$survey) jsonResponse(['error' => 'ไม่พบแบบสอบถาม'], 404);
|
||||
|
||||
// Check access
|
||||
if (!isAdmin() && $survey['user_id'] != $_SESSION['user_id']) {
|
||||
jsonResponse(['error' => 'ไม่มีสิทธิ์เข้าถึง'], 403);
|
||||
}
|
||||
|
||||
// Get sections with questions
|
||||
$survey['sections'] = getSections($db, $id);
|
||||
|
||||
jsonResponse(['survey' => $survey]);
|
||||
}
|
||||
|
||||
function getByLink() {
|
||||
$link = $_GET['link'] ?? '';
|
||||
if (empty($link)) jsonResponse(['error' => 'Missing link'], 400);
|
||||
|
||||
$db = getDB();
|
||||
$stmt = $db->prepare("SELECT * FROM surveys WHERE unique_link = ? AND status = 'active'");
|
||||
$stmt->execute([$link]);
|
||||
$survey = $stmt->fetch();
|
||||
|
||||
if (!$survey) jsonResponse(['error' => 'ไม่พบแบบสอบถามหรือแบบสอบถามถูกปิด'], 404);
|
||||
|
||||
$survey['sections'] = getSections($db, $survey['id']);
|
||||
|
||||
// Remove sensitive data
|
||||
unset($survey['user_id']);
|
||||
|
||||
jsonResponse(['survey' => $survey]);
|
||||
}
|
||||
|
||||
function getSections($db, $surveyId) {
|
||||
$stmt = $db->prepare("SELECT * FROM survey_sections WHERE survey_id = ? ORDER BY section_number");
|
||||
$stmt->execute([$surveyId]);
|
||||
$sections = $stmt->fetchAll();
|
||||
|
||||
foreach ($sections as &$section) {
|
||||
// Load headings for this section
|
||||
$hStmt = $db->prepare("SELECT * FROM survey_headings WHERE section_id = ? ORDER BY order_num");
|
||||
$hStmt->execute([$section['id']]);
|
||||
$headings = $hStmt->fetchAll();
|
||||
|
||||
// Load ALL questions for this section
|
||||
$qStmt = $db->prepare("SELECT * FROM survey_questions WHERE section_id = ? ORDER BY order_num");
|
||||
$qStmt->execute([$section['id']]);
|
||||
$allQuestions = $qStmt->fetchAll();
|
||||
|
||||
// Separate questions: those with heading_id vs those without
|
||||
$looseQuestions = [];
|
||||
$headingQuestionsMap = [];
|
||||
foreach ($allQuestions as &$q) {
|
||||
$q['is_required'] = (int)$q['is_required'];
|
||||
if ($q['options']) {
|
||||
$q['options'] = json_decode($q['options'], true);
|
||||
}
|
||||
if ($q['heading_id']) {
|
||||
$headingQuestionsMap[$q['heading_id']][] = $q;
|
||||
} else {
|
||||
$looseQuestions[] = $q;
|
||||
}
|
||||
}
|
||||
|
||||
// Attach questions to their headings
|
||||
foreach ($headings as &$h) {
|
||||
$h['questions'] = $headingQuestionsMap[$h['id']] ?? [];
|
||||
}
|
||||
|
||||
$section['headings'] = $headings;
|
||||
$section['questions'] = $looseQuestions; // questions not in any heading
|
||||
}
|
||||
|
||||
return $sections;
|
||||
}
|
||||
|
||||
function insertQuestion($db, $sectionId, $headingId, $q, $orderNum) {
|
||||
$options = isset($q['options']) ? json_encode($q['options'], JSON_UNESCAPED_UNICODE) : null;
|
||||
$stmt = $db->prepare("INSERT INTO survey_questions (section_id, heading_id, question_text, question_type, is_required, order_num, options, scale_min, scale_max, description) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([
|
||||
$sectionId,
|
||||
$headingId,
|
||||
$q['question_text'],
|
||||
$q['question_type'] ?? 'radio',
|
||||
$q['is_required'] ?? 1,
|
||||
$q['order_num'] ?? $orderNum,
|
||||
$options,
|
||||
$q['scale_min'] ?? 1,
|
||||
$q['scale_max'] ?? 5,
|
||||
$q['description'] ?? null
|
||||
]);
|
||||
return $db->lastInsertId();
|
||||
}
|
||||
|
||||
function createSurvey() {
|
||||
$input = getInput();
|
||||
$title = trim($input['title'] ?? '');
|
||||
$description = trim($input['description'] ?? '');
|
||||
$projectId = $input['project_id'] ?? null;
|
||||
|
||||
if (empty($title)) {
|
||||
jsonResponse(['error' => 'กรุณากรอกชื่อแบบสอบถาม'], 400);
|
||||
}
|
||||
|
||||
$hasCertificate = (int)($input['has_certificate'] ?? 0);
|
||||
$certificateTemplate = $input['certificate_template'] ?? null;
|
||||
$certNameY = isset($input['cert_name_y']) ? (double)$input['cert_name_y'] : 82.5;
|
||||
$certCodeY = isset($input['cert_code_y']) ? (double)$input['cert_code_y'] : 190.5;
|
||||
|
||||
$db = getDB();
|
||||
$uniqueLink = bin2hex(random_bytes(16));
|
||||
|
||||
$db->beginTransaction();
|
||||
try {
|
||||
$stmt = $db->prepare("INSERT INTO surveys (user_id, project_id, title, description, unique_link, has_certificate, certificate_template, cert_name_y, cert_code_y) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$_SESSION['user_id'], $projectId, $title, $description, $uniqueLink, $hasCertificate, $certificateTemplate, $certNameY, $certCodeY]);
|
||||
$surveyId = $db->lastInsertId();
|
||||
|
||||
// Create default sections with pre-defined questions based on user requirements
|
||||
$sections = $input['sections'] ?? [
|
||||
[
|
||||
'section_number' => 1,
|
||||
'title' => 'ส่วนที่ ข้อมูลทั่วไป',
|
||||
'description' => 'ข้อมูลพื้นฐานของผู้ตอบ (ต้องตอบทุกข้อ)',
|
||||
'questions' => [
|
||||
['question_text' => 'เพศ', 'question_type' => 'radio', 'options' => ['ชาย', 'หญิง'], 'is_required' => 1],
|
||||
['question_text' => 'ช่วงอายุ', 'question_type' => 'dropdown', 'options' => ['18-25 ปี', '26-35 ปี', '36-45 ปี', '46-55 ปี', '56 ปีขึ้นไป'], 'is_required' => 1],
|
||||
['question_text' => 'คณะ / หน่วยงาน', 'question_type' => 'text', 'is_required' => 1]
|
||||
]
|
||||
],
|
||||
[
|
||||
'section_number' => 2,
|
||||
'title' => 'ส่วนที่ การประเมินความพึงพอใจ',
|
||||
'description' => 'ประเมินระดับความพึงพอใจ (5 = มากที่สุด, 1 = น้อยที่สุด)',
|
||||
'questions' => [
|
||||
['question_text' => 'ด้านเนื้อหาของการจัดโครงการ', 'question_type' => 'radio', 'is_required' => 1],
|
||||
['question_text' => 'ด้านวิทยากร / ผู้บรรยาย', 'question_type' => 'radio', 'is_required' => 1],
|
||||
['question_text' => 'ด้านสถานที่และความสะดวก', 'question_type' => 'radio', 'is_required' => 1]
|
||||
]
|
||||
],
|
||||
[
|
||||
'section_number' => 3,
|
||||
'title' => 'ส่วนที่ ข้อเสนอแนะ',
|
||||
'description' => 'ข้อเสนอแนะเพิ่มเติม',
|
||||
'questions' => [
|
||||
['question_text' => 'ข้อเสนอแนะอื่นๆ', 'question_type' => 'text', 'is_required' => 0]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
foreach ($sections as $section) {
|
||||
$stmt = $db->prepare("INSERT INTO survey_sections (survey_id, section_number, title, description, category) VALUES (?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$surveyId, $section['section_number'], $section['title'], $section['description'] ?? '', $section['category'] ?? 'rating']);
|
||||
$sectionId = $db->lastInsertId();
|
||||
|
||||
// Insert headings and their questions
|
||||
if (isset($section['headings'])) {
|
||||
foreach ($section['headings'] as $hi => $heading) {
|
||||
$hStmt = $db->prepare("INSERT INTO survey_headings (section_id, title, description, order_num) VALUES (?, ?, ?, ?)");
|
||||
$hStmt->execute([$sectionId, $heading['title'], $heading['description'] ?? null, $heading['order_num'] ?? $hi]);
|
||||
$headingId = $db->lastInsertId();
|
||||
|
||||
if (isset($heading['questions'])) {
|
||||
foreach ($heading['questions'] as $i => $q) {
|
||||
insertQuestion($db, $sectionId, $headingId, $q, $i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Insert loose questions (not in any heading)
|
||||
if (isset($section['questions'])) {
|
||||
foreach ($section['questions'] as $i => $q) {
|
||||
insertQuestion($db, $sectionId, null, $q, $i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$db->commit();
|
||||
jsonResponse(['success' => true, 'id' => $surveyId, 'unique_link' => $uniqueLink, 'message' => 'สร้างแบบสอบถามสำเร็จ'], 201);
|
||||
} catch (Exception $e) {
|
||||
$db->rollBack();
|
||||
jsonResponse(['error' => 'เกิดข้อผิดพลาด: ' . $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
function updateSurvey() {
|
||||
$input = getInput();
|
||||
$id = (int)($input['id'] ?? 0);
|
||||
if (!$id) jsonResponse(['error' => 'Missing ID'], 400);
|
||||
|
||||
$db = getDB();
|
||||
|
||||
// Check ownership
|
||||
$stmt = $db->prepare("SELECT user_id FROM surveys WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$survey = $stmt->fetch();
|
||||
if (!$survey) jsonResponse(['error' => 'ไม่พบแบบสอบถาม'], 404);
|
||||
if (!isAdmin() && $survey['user_id'] != $_SESSION['user_id']) {
|
||||
jsonResponse(['error' => 'ไม่มีสิทธิ์'], 403);
|
||||
}
|
||||
|
||||
$db->beginTransaction();
|
||||
try {
|
||||
// Update survey info
|
||||
if (isset($input['title'])) {
|
||||
$hasCertificate = (int)($input['has_certificate'] ?? 0);
|
||||
$certificateTemplate = $input['certificate_template'] ?? null;
|
||||
$certNameY = isset($input['cert_name_y']) ? (double)$input['cert_name_y'] : 82.5;
|
||||
$certCodeY = isset($input['cert_code_y']) ? (double)$input['cert_code_y'] : 190.5;
|
||||
$stmt = $db->prepare("UPDATE surveys SET title = ?, description = ?, has_certificate = ?, certificate_template = ?, cert_name_y = ?, cert_code_y = ? WHERE id = ?");
|
||||
$stmt->execute([$input['title'], $input['description'] ?? '', $hasCertificate, $certificateTemplate, $certNameY, $certCodeY, $id]);
|
||||
}
|
||||
|
||||
// Update sections and questions
|
||||
if (isset($input['sections'])) {
|
||||
// Delete old headings first (they reference sections)
|
||||
$db->prepare("DELETE h FROM survey_headings h JOIN survey_sections s ON h.section_id = s.id WHERE s.survey_id = ?")->execute([$id]);
|
||||
// Delete old sections (cascades to questions)
|
||||
$db->prepare("DELETE FROM survey_sections WHERE survey_id = ?")->execute([$id]);
|
||||
|
||||
foreach ($input['sections'] as $section) {
|
||||
$stmt = $db->prepare("INSERT INTO survey_sections (survey_id, section_number, title, description, category) VALUES (?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$id, $section['section_number'], $section['title'], $section['description'] ?? '', $section['category'] ?? 'rating']);
|
||||
$sectionId = $db->lastInsertId();
|
||||
|
||||
// Insert headings and their questions
|
||||
if (isset($section['headings'])) {
|
||||
foreach ($section['headings'] as $hi => $heading) {
|
||||
$hStmt = $db->prepare("INSERT INTO survey_headings (section_id, title, description, order_num) VALUES (?, ?, ?, ?)");
|
||||
$hStmt->execute([$sectionId, $heading['title'], $heading['description'] ?? null, $heading['order_num'] ?? $hi]);
|
||||
$headingId = $db->lastInsertId();
|
||||
|
||||
if (isset($heading['questions'])) {
|
||||
foreach ($heading['questions'] as $i => $q) {
|
||||
insertQuestion($db, $sectionId, $headingId, $q, $i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Insert loose questions (not in any heading)
|
||||
if (isset($section['questions'])) {
|
||||
foreach ($section['questions'] as $i => $q) {
|
||||
insertQuestion($db, $sectionId, null, $q, $i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$db->commit();
|
||||
jsonResponse(['success' => true, 'message' => 'อัปเดตแบบสอบถามสำเร็จ']);
|
||||
} catch (Exception $e) {
|
||||
$db->rollBack();
|
||||
jsonResponse(['error' => 'เกิดข้อผิดพลาด: ' . $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
function deleteSurvey() {
|
||||
$input = getInput();
|
||||
$id = (int)($input['id'] ?? 0);
|
||||
if (!$id) jsonResponse(['error' => 'Missing ID'], 400);
|
||||
|
||||
$db = getDB();
|
||||
$stmt = $db->prepare("SELECT user_id FROM surveys WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$survey = $stmt->fetch();
|
||||
|
||||
if (!$survey) jsonResponse(['error' => 'ไม่พบแบบสอบถาม'], 404);
|
||||
if (!isAdmin() && $survey['user_id'] != $_SESSION['user_id']) {
|
||||
jsonResponse(['error' => 'ไม่มีสิทธิ์'], 403);
|
||||
}
|
||||
|
||||
$db->prepare("DELETE FROM surveys WHERE id = ?")->execute([$id]);
|
||||
jsonResponse(['success' => true, 'message' => 'ลบแบบสอบถามสำเร็จ']);
|
||||
}
|
||||
|
||||
function generateLink() {
|
||||
$input = getInput();
|
||||
$id = (int)($input['id'] ?? 0);
|
||||
if (!$id) jsonResponse(['error' => 'Missing ID'], 400);
|
||||
|
||||
$db = getDB();
|
||||
$newLink = bin2hex(random_bytes(16));
|
||||
$stmt = $db->prepare("UPDATE surveys SET unique_link = ?, status = 'active' WHERE id = ?");
|
||||
$stmt->execute([$newLink, $id]);
|
||||
|
||||
$baseUrl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'];
|
||||
$fullLink = $baseUrl . '/survey/respond.php?link=' . $newLink;
|
||||
|
||||
jsonResponse(['success' => true, 'unique_link' => $newLink, 'full_url' => $fullLink]);
|
||||
}
|
||||
|
||||
function updateStatus() {
|
||||
$input = getInput();
|
||||
$id = (int)($input['id'] ?? 0);
|
||||
$status = $input['status'] ?? '';
|
||||
|
||||
if (!$id || !in_array($status, ['draft', 'active', 'closed'])) {
|
||||
jsonResponse(['error' => 'Invalid input'], 400);
|
||||
}
|
||||
|
||||
$db = getDB();
|
||||
$stmt = $db->prepare("UPDATE surveys SET status = ? WHERE id = ?");
|
||||
$stmt->execute([$status, $id]);
|
||||
|
||||
jsonResponse(['success' => true, 'message' => 'อัปเดตสถานะสำเร็จ']);
|
||||
}
|
||||
|
||||
function duplicateSurvey() {
|
||||
$input = getInput();
|
||||
$id = (int)($input['id'] ?? 0);
|
||||
if (!$id) jsonResponse(['error' => 'Missing ID'], 400);
|
||||
|
||||
$db = getDB();
|
||||
$stmt = $db->prepare("SELECT * FROM surveys WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$survey = $stmt->fetch();
|
||||
|
||||
if (!$survey) jsonResponse(['error' => 'ไม่พบแบบสอบถาม'], 404);
|
||||
|
||||
$db->beginTransaction();
|
||||
try {
|
||||
$uniqueLink = bin2hex(random_bytes(16));
|
||||
$stmt = $db->prepare("INSERT INTO surveys (user_id, project_id, title, description, unique_link, status, has_certificate, certificate_template, cert_name_y, cert_code_y) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([
|
||||
$_SESSION['user_id'],
|
||||
$survey['project_id'],
|
||||
"สำเนาของ " . $survey['title'],
|
||||
$survey['description'],
|
||||
$uniqueLink,
|
||||
'draft',
|
||||
$survey['has_certificate'],
|
||||
$survey['certificate_template'],
|
||||
$survey['cert_name_y'],
|
||||
$survey['cert_code_y']
|
||||
]);
|
||||
$newSurveyId = $db->lastInsertId();
|
||||
|
||||
// Copy sections
|
||||
$stmt = $db->prepare("SELECT * FROM survey_sections WHERE survey_id = ? ORDER BY section_number");
|
||||
$stmt->execute([$id]);
|
||||
$sections = $stmt->fetchAll();
|
||||
|
||||
foreach ($sections as $section) {
|
||||
$stmtInsert = $db->prepare("INSERT INTO survey_sections (survey_id, section_number, title, description, category) VALUES (?, ?, ?, ?, ?)");
|
||||
$stmtInsert->execute([$newSurveyId, $section['section_number'], $section['title'], $section['description'], $section['category']]);
|
||||
$newSectionId = $db->lastInsertId();
|
||||
|
||||
// Copy headings
|
||||
$stmtH = $db->prepare("SELECT * FROM survey_headings WHERE section_id = ? ORDER BY order_num");
|
||||
$stmtH->execute([$section['id']]);
|
||||
$headings = $stmtH->fetchAll();
|
||||
$headingIdMap = []; // old heading id -> new heading id
|
||||
|
||||
foreach ($headings as $h) {
|
||||
$stmtInsertH = $db->prepare("INSERT INTO survey_headings (section_id, title, description, order_num) VALUES (?, ?, ?, ?)");
|
||||
$stmtInsertH->execute([$newSectionId, $h['title'], $h['description'], $h['order_num']]);
|
||||
$headingIdMap[$h['id']] = $db->lastInsertId();
|
||||
}
|
||||
|
||||
// Copy questions
|
||||
$stmtQ = $db->prepare("SELECT * FROM survey_questions WHERE section_id = ? ORDER BY order_num");
|
||||
$stmtQ->execute([$section['id']]);
|
||||
$questions = $stmtQ->fetchAll();
|
||||
|
||||
foreach ($questions as $q) {
|
||||
$newHeadingId = $q['heading_id'] ? ($headingIdMap[$q['heading_id']] ?? null) : null;
|
||||
$stmtInsertQ = $db->prepare("INSERT INTO survey_questions (section_id, heading_id, question_text, question_type, is_required, order_num, options, scale_min, scale_max, description) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmtInsertQ->execute([
|
||||
$newSectionId,
|
||||
$newHeadingId,
|
||||
$q['question_text'],
|
||||
$q['question_type'],
|
||||
$q['is_required'],
|
||||
$q['order_num'],
|
||||
$q['options'],
|
||||
$q['scale_min'],
|
||||
$q['scale_max'],
|
||||
$q['description']
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$db->commit();
|
||||
jsonResponse(['success' => true, 'id' => $newSurveyId, 'message' => 'คัดลอกแบบสอบถามสำเร็จ']);
|
||||
} catch (Exception $e) {
|
||||
$db->rollBack();
|
||||
jsonResponse(['error' => 'เกิดข้อผิดพลาด: ' . $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
function uploadTemplate() {
|
||||
if (!isset($_FILES['template_image'])) {
|
||||
jsonResponse(['error' => 'ไม่พบไฟล์ที่อัปโหลด'], 400);
|
||||
}
|
||||
|
||||
$file = $_FILES['template_image'];
|
||||
if ($file['error'] !== UPLOAD_ERR_OK) {
|
||||
jsonResponse(['error' => 'การอัปโหลดไฟล์ล้มเหลว: รหัสข้อผิดพลาด ' . $file['error']], 400);
|
||||
}
|
||||
|
||||
// ตรวจสอบขนาดไฟล์ (สูงสุด 5MB)
|
||||
if ($file['size'] > 5 * 1024 * 1024) {
|
||||
jsonResponse(['error' => 'ขนาดไฟล์รูปภาพต้องไม่เกิน 5MB'], 400);
|
||||
}
|
||||
|
||||
// ตรวจสอบนามสกุล/ประเภทไฟล์ (ต้องเป็น PNG หรือ JPEG เท่านั้น)
|
||||
$imageInfo = getimagesize($file['tmp_name']);
|
||||
if ($imageInfo === false) {
|
||||
jsonResponse(['error' => 'ไฟล์ที่อัปโหลดไม่ใช่รูปภาพที่ถูกต้อง'], 400);
|
||||
}
|
||||
|
||||
$mimeType = $imageInfo['mime'];
|
||||
$allowedMimeTypes = ['image/png', 'image/jpeg', 'image/jpg'];
|
||||
if (!in_array($mimeType, $allowedMimeTypes)) {
|
||||
jsonResponse(['error' => 'อนุญาตให้อัปโหลดเฉพาะไฟล์รูปภาพ PNG หรือ JPG/JPEG เท่านั้น'], 400);
|
||||
}
|
||||
|
||||
// สร้างโฟลเดอร์สำหรับเก็บไฟล์รูปภาพ (ถ้ายังไม่มี)
|
||||
$uploadDir = __DIR__ . '/../uploads/templates';
|
||||
if (!is_dir($uploadDir)) {
|
||||
mkdir($uploadDir, 0777, true);
|
||||
}
|
||||
chmod($uploadDir, 0777);
|
||||
|
||||
// สร้างชื่อไฟล์ใหม่เพื่อป้องกันไฟล์ซ้ำและแฮกเกอร์
|
||||
$ext = ($mimeType === 'image/png') ? 'png' : 'jpg';
|
||||
$fileName = 'tpl_' . time() . '_' . bin2hex(random_bytes(8)) . '.' . $ext;
|
||||
$destPath = $uploadDir . '/' . $fileName;
|
||||
|
||||
if (move_uploaded_file($file['tmp_name'], $destPath)) {
|
||||
chmod($destPath, 0666);
|
||||
$relativePath = 'uploads/templates/' . $fileName;
|
||||
jsonResponse(['success' => true, 'template_path' => $relativePath, 'message' => 'อัปโหลดเทมเพลตสำเร็จ']);
|
||||
} else {
|
||||
jsonResponse(['error' => 'ไม่สามารถย้ายไฟล์ไปยังโฟลเดอร์ปลายทางได้'], 500);
|
||||
}
|
||||
}
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
/**
|
||||
* Users CRUD API (Admin only)
|
||||
* GET ?action=list — List all users
|
||||
* GET ?action=get&id=X — Get user by ID
|
||||
* POST ?action=create — Create user
|
||||
* POST ?action=update — Update user
|
||||
* POST ?action=delete — Delete user
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
require_once __DIR__ . '/../config/auth.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
requireAdmin();
|
||||
|
||||
$action = $_GET['action'] ?? '';
|
||||
|
||||
switch ($action) {
|
||||
case 'list':
|
||||
listUsers();
|
||||
break;
|
||||
case 'get':
|
||||
getUser();
|
||||
break;
|
||||
case 'create':
|
||||
createUser();
|
||||
break;
|
||||
case 'update':
|
||||
updateUser();
|
||||
break;
|
||||
case 'delete':
|
||||
deleteUser();
|
||||
break;
|
||||
default:
|
||||
jsonResponse(['error' => 'Invalid action'], 400);
|
||||
}
|
||||
|
||||
function listUsers() {
|
||||
$db = getDB();
|
||||
$stmt = $db->query("SELECT id, username, role, full_name, personnel_code, subject_area, branch_code, email, created_at FROM users ORDER BY created_at DESC");
|
||||
jsonResponse(['users' => $stmt->fetchAll()]);
|
||||
}
|
||||
|
||||
function getUser() {
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
if (!$id) jsonResponse(['error' => 'Missing ID'], 400);
|
||||
|
||||
$db = getDB();
|
||||
$stmt = $db->prepare("SELECT id, username, role, full_name, personnel_code, subject_area, branch_code, email, created_at FROM users WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if (!$user) jsonResponse(['error' => 'ไม่พบผู้ใช้'], 404);
|
||||
jsonResponse(['user' => $user]);
|
||||
}
|
||||
|
||||
function createUser() {
|
||||
$input = getInput();
|
||||
$username = trim($input['username'] ?? '');
|
||||
$password = trim($input['password'] ?? '');
|
||||
$role = $input['role'] ?? 'teacher';
|
||||
$fullName = trim($input['full_name'] ?? '');
|
||||
$personnelCode = trim($input['personnel_code'] ?? '');
|
||||
$subjectArea = trim($input['subject_area'] ?? '');
|
||||
$branchCode = trim($input['branch_code'] ?? '');
|
||||
$email = trim($input['email'] ?? '');
|
||||
|
||||
if (empty($username) || empty($password) || empty($fullName)) {
|
||||
jsonResponse(['error' => 'กรุณากรอกข้อมูลให้ครบถ้วน'], 400);
|
||||
}
|
||||
|
||||
$db = getDB();
|
||||
|
||||
// Check duplicate username
|
||||
$stmt = $db->prepare("SELECT id FROM users WHERE username = ?");
|
||||
$stmt->execute([$username]);
|
||||
if ($stmt->fetch()) {
|
||||
jsonResponse(['error' => 'ชื่อผู้ใช้นี้มีอยู่แล้ว'], 409);
|
||||
}
|
||||
|
||||
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
|
||||
|
||||
$stmt = $db->prepare("INSERT INTO users (username, password_hash, role, full_name, personnel_code, subject_area, branch_code, email) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$username, $passwordHash, $role, $fullName, $personnelCode, $subjectArea, $branchCode, $email]);
|
||||
|
||||
jsonResponse(['success' => true, 'id' => $db->lastInsertId(), 'message' => 'สร้างผู้ใช้สำเร็จ'], 201);
|
||||
}
|
||||
|
||||
function updateUser() {
|
||||
$input = getInput();
|
||||
$id = (int)($input['id'] ?? 0);
|
||||
if (!$id) jsonResponse(['error' => 'Missing ID'], 400);
|
||||
|
||||
$fullName = trim($input['full_name'] ?? '');
|
||||
$role = $input['role'] ?? '';
|
||||
$personnelCode = trim($input['personnel_code'] ?? '');
|
||||
$subjectArea = trim($input['subject_area'] ?? '');
|
||||
$branchCode = trim($input['branch_code'] ?? '');
|
||||
|
||||
$db = getDB();
|
||||
|
||||
$fields = [];
|
||||
$params = [];
|
||||
|
||||
if (!empty($fullName)) { $fields[] = "full_name = ?"; $params[] = $fullName; }
|
||||
if (!empty($role)) { $fields[] = "role = ?"; $params[] = $role; }
|
||||
if ($personnelCode !== '') { $fields[] = "personnel_code = ?"; $params[] = $personnelCode; }
|
||||
if ($subjectArea !== '') { $fields[] = "subject_area = ?"; $params[] = $subjectArea; }
|
||||
if ($branchCode !== '') { $fields[] = "branch_code = ?"; $params[] = $branchCode; }
|
||||
if (isset($input['email'])) { $fields[] = "email = ?"; $params[] = trim($input['email']); }
|
||||
|
||||
// Update password if provided
|
||||
if (!empty($input['password'])) {
|
||||
$fields[] = "password_hash = ?";
|
||||
$params[] = password_hash($input['password'], PASSWORD_DEFAULT);
|
||||
}
|
||||
|
||||
if (empty($fields)) {
|
||||
jsonResponse(['error' => 'ไม่มีข้อมูลที่ต้องอัปเดต'], 400);
|
||||
}
|
||||
|
||||
$params[] = $id;
|
||||
$sql = "UPDATE users SET " . implode(', ', $fields) . " WHERE id = ?";
|
||||
$stmt = $db->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
|
||||
jsonResponse(['success' => true, 'message' => 'อัปเดตผู้ใช้สำเร็จ']);
|
||||
}
|
||||
|
||||
function deleteUser() {
|
||||
$input = getInput();
|
||||
$id = (int)($input['id'] ?? 0);
|
||||
if (!$id) jsonResponse(['error' => 'Missing ID'], 400);
|
||||
|
||||
// Don't delete yourself
|
||||
if ($id == $_SESSION['user_id']) {
|
||||
jsonResponse(['error' => 'ไม่สามารถลบบัญชีตัวเองได้'], 400);
|
||||
}
|
||||
|
||||
$db = getDB();
|
||||
$stmt = $db->prepare("DELETE FROM users WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
|
||||
jsonResponse(['success' => true, 'message' => 'ลบผู้ใช้สำเร็จ']);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
/**
|
||||
* Certificate Verification API
|
||||
* GET ?code=TPU-2569-0016 — Verify a certificate by its unique code
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$code = trim($_GET['code'] ?? '');
|
||||
|
||||
if (empty($code)) {
|
||||
jsonResponse(['success' => false, 'error' => 'กรุณาระบุรหัสตรวจสอบเกียรติบัตร'], 400);
|
||||
}
|
||||
|
||||
$db = getDB();
|
||||
|
||||
// ค้นหารหัสเกียรติบัตรในฐานข้อมูล
|
||||
$stmt = $db->prepare("
|
||||
SELECT sr.id, sr.respondent_info, sr.submitted_at, s.title as survey_title
|
||||
FROM survey_responses sr
|
||||
JOIN surveys s ON sr.survey_id = s.id
|
||||
WHERE sr.cert_code = ?
|
||||
");
|
||||
$stmt->execute([$code]);
|
||||
$record = $stmt->fetch();
|
||||
|
||||
if (!$record) {
|
||||
jsonResponse(['success' => false, 'error' => 'ไม่พบรหัสเกียรติบัตรนี้ในระบบ หรือรหัสอาจไม่ถูกต้อง'], 404);
|
||||
}
|
||||
|
||||
// ถอดรหัสชื่อผู้เข้าร่วม
|
||||
$respondentInfo = json_decode($record['respondent_info'], true) ?: [];
|
||||
$name = $respondentInfo['name'] ?? '';
|
||||
|
||||
// ถ้ารายละเอียดของชื่อว่าง (เช่น กรณีข้อมูลเก่าที่ใช้ Fallback)
|
||||
// ให้พยายามดึงข้อมูลจากตารางคำตอบจริงที่มีคำถามเกี่ยวกับชื่อ
|
||||
if (empty($name)) {
|
||||
$stmtQ = $db->prepare("
|
||||
SELECT q.id
|
||||
FROM survey_questions q
|
||||
JOIN survey_sections s ON q.section_id = s.id
|
||||
WHERE s.survey_id = (SELECT survey_id FROM survey_responses WHERE id = ?)
|
||||
AND (q.question_text LIKE '%ชื่อ%' OR q.question_text LIKE '%นามสกุล%')
|
||||
LIMIT 1
|
||||
");
|
||||
$stmtQ->execute([$record['id']]);
|
||||
$qRow = $stmtQ->fetch();
|
||||
if ($qRow) {
|
||||
$nameQuestionId = $qRow['id'];
|
||||
$stmtA = $db->prepare("
|
||||
SELECT answer_value FROM response_answers
|
||||
WHERE response_id = ? AND question_id = ?
|
||||
");
|
||||
$stmtA->execute([$record['id'], $nameQuestionId]);
|
||||
$aRow = $stmtA->fetch();
|
||||
if ($aRow) {
|
||||
$name = trim($aRow['answer_value']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($name)) {
|
||||
$name = 'ผู้เข้าร่วมโครงการ';
|
||||
}
|
||||
|
||||
// ส่งรายละเอียดกลับไปหาผู้ขอตรวจสอบ
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'code' => $code,
|
||||
'recipient_name' => $name,
|
||||
'course_title' => $record['survey_title'],
|
||||
'issue_date' => $record['submitted_at']
|
||||
]);
|
||||
Reference in New Issue
Block a user