Initial commit - TPU Survey System with Google Forms & Certificate Generator
This commit is contained in:
+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>';
|
||||
}
|
||||
Reference in New Issue
Block a user