'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 $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, q.options 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); } // Helper: normalize text for comparison 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); } // Helper to parse Likert rating values from numbers or Thai text function parseRatingScore($str) { $trimmed = trim((string)$str); if ($trimmed === '') return null; if (is_numeric($trimmed)) { $num = (int)$trimmed; return ($num >= 1 && $num <= 5) ? $num : null; } if (preg_match('/^([1-5])\s*[\.\:\-\(\s]/u', $trimmed, $m)) { return (int)$m[1]; } if (mb_strpos($trimmed, 'มากที่สุด') !== false || mb_strpos($trimmed, 'ดีมาก') !== false || mb_strpos($trimmed, 'ยอดเยี่ยม') !== false || mb_strpos($trimmed, 'ดีเยี่ยม') !== false || mb_strpos($trimmed, 'เห็นด้วยอย่างยิ่ง') !== false) { return 5; } if (mb_strpos($trimmed, 'น้อยที่สุด') !== false || mb_strpos($trimmed, 'ไม่เห็นด้วยอย่างยิ่ง') !== false) { return 1; } if (mb_strpos($trimmed, 'มาก') !== false || mb_strpos($trimmed, 'ดี') !== false || mb_strpos($trimmed, 'เห็นด้วย') !== false) { return 4; } if (mb_strpos($trimmed, 'ปานกลาง') !== false || mb_strpos($trimmed, 'พอใช้') !== false || mb_strpos($trimmed, 'ไม่แน่ใจ') !== false) { return 3; } if (mb_strpos($trimmed, 'น้อย') !== false || mb_strpos($trimmed, 'ควรปรับปรุง') !== false || mb_strpos($trimmed, 'ต้องปรับปรุง') !== false || mb_strpos($trimmed, 'ไม่เห็นด้วย') !== false) { return 2; } if (mb_strpos($trimmed, 'ปรับปรุง') !== false) { return 1; } return null; } // Map CSV Column Index -> question_id $colToQuestionMap = []; $questionsById = []; foreach ($questions as $q) { $questionsById[$q['id']] = $q; } foreach ($cleanHeaders as $colIdx => $headerText) { if (empty($headerText) || strtolower($headerText) === 'timestamp' || strtolower($headerText) === 'ประทับเวลา') { continue; } $normHeader = normalizeText($headerText); // Extract inside bracket if Google Forms grid header e.g. "Heading [Question]" $bracketText = ''; if (preg_match('/\[(.*?)\]/', $headerText, $m)) { $bracketText = normalizeText($m[1]); } // Try exact or normalized matching against survey questions foreach ($questions as $q) { $normQText = normalizeText($q['question_text']); if ( $normHeader === $normQText || ($bracketText !== '' && ($bracketText === $normQText || strpos($bracketText, $normQText) !== false || strpos($normQText, $bracketText) !== false)) || 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) < count($questions) / 2) { $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 !== '') { $targetQ = $questionsById[$qId] ?? null; // Normalize rating score if it's a 5-point rating scale question if ($targetQ && $targetQ['question_type'] === 'radio' && empty($targetQ['options'])) { $score = parseRatingScore($val); if ($score !== null) { $val = (string)$score; } } $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); }