db = Database::getInstance(); } public static function getInstance() { if (self::$instance === null) { self::$instance = new self(); } return self::$instance; } /** * Get all surveys for a user with summary statistics */ public function getSurveys($userId, $filters = []) { try { $where = ['s.created_by = ?']; $params = [$userId]; $types = 'i'; if (!empty($filters['status'])) { $where[] = 's.status = ?'; $params[] = $filters['status']; $types .= 's'; } if (!empty($filters['search'])) { $where[] = '(s.title LIKE ? OR s.description LIKE ?)'; $search = "%{$filters['search']}%"; $params[] = $search; $params[] = $search; $types .= 'ss'; } $whereClause = implode(' AND ', $where); $sql = "SELECT s.*, (SELECT COUNT(DISTINCT t.id) FROM survey_tokens t WHERE t.survey_id = s.id) as tokens_count, (SELECT COUNT(DISTINCT t.id) FROM survey_tokens t WHERE t.survey_id = s.id AND t.is_completed = 1) as responses_count, (SELECT COUNT(DISTINCT q.id) FROM survey_questions q WHERE q.survey_id = s.id) as questions_count FROM surveys s WHERE $whereClause ORDER BY s.created_at DESC"; $stmt = $this->db->prepare($sql); $stmt->bind_param($types, ...$params); $stmt->execute(); $result = $stmt->get_result(); return $result->fetch_all(MYSQLI_ASSOC); } catch (Exception $e) { error_log("Get surveys error: " . $e->getMessage()); return []; } } /** * Get questions for a specific survey */ public function getSurveyQuestions($surveyId) { try { $sql = "SELECT * FROM survey_questions WHERE survey_id = ? ORDER BY question_order ASC"; $stmt = $this->db->prepare($sql); $stmt->bind_param('i', $surveyId); $stmt->execute(); $questions = $stmt->get_result()->fetch_all(MYSQLI_ASSOC); // Parse JSON fields for each question foreach ($questions as &$question) { if (!empty($question['options'])) { $question['options'] = json_decode($question['options'], true); } if (!empty($question['config'])) { $question['config'] = json_decode($question['config'], true); } } return $questions; } catch (Exception $e) { error_log("Get survey questions error: " . $e->getMessage()); return []; } } /** * Create a new survey */ public function createSurvey($data) { try { $sql = "INSERT INTO surveys ( title, description, status, allow_multiple, allow_edit, created_by ) VALUES (?, ?, ?, ?, ?, ?)"; $stmt = $this->db->prepare($sql); $stmt->bind_param('sssiii', $data['title'], $data['description'], $data['status'], $data['allow_multiple'], $data['allow_edit'], $data['created_by'] ); if (!$stmt->execute()) { throw new Exception("Failed to create survey"); } return [ 'success' => true, 'survey_id' => $this->db->getLastInsertId() ]; } catch (Exception $e) { error_log("Create survey error: " . $e->getMessage()); return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * Get survey details with questions */ public function getSurveyDetails($surveyId, $userId) { try { // Get survey details $sql = "SELECT * FROM surveys WHERE id = ? AND created_by = ?"; $stmt = $this->db->prepare($sql); $stmt->bind_param('ii', $surveyId, $userId); $stmt->execute(); $survey = $stmt->get_result()->fetch_assoc(); if (!$survey) { return null; } // Get questions $sql = "SELECT * FROM survey_questions WHERE survey_id = ? ORDER BY question_order ASC"; $stmt = $this->db->prepare($sql); $stmt->bind_param('i', $surveyId); $stmt->execute(); $survey['questions'] = $stmt->get_result()->fetch_all(MYSQLI_ASSOC); // Get response statistics $sql = "SELECT COUNT(DISTINCT st.id) as total_tokens, SUM(st.is_completed) as completed_responses, COUNT(DISTINCT sr.selection_id) as unique_responses FROM survey_tokens st LEFT JOIN survey_responses sr ON sr.survey_id = st.survey_id WHERE st.survey_id = ?"; $stmt = $this->db->prepare($sql); $stmt->bind_param('i', $surveyId); $stmt->execute(); $survey['stats'] = $stmt->get_result()->fetch_assoc(); return $survey; } catch (Exception $e) { error_log("Get survey details error: " . $e->getMessage()); return null; } } /** * Update survey details */ public function updateSurvey($surveyId, $data) { try { $sql = "UPDATE surveys SET title = ?, description = ?, status = ?, allow_multiple = ?, allow_edit = ? WHERE id = ? AND created_by = ?"; $stmt = $this->db->prepare($sql); $stmt->bind_param('sssiiii', $data['title'], $data['description'], $data['status'], $data['allow_multiple'], $data['allow_edit'], $surveyId, $data['created_by'] ); if (!$stmt->execute()) { throw new Exception("Failed to update survey"); } return ['success' => true]; } catch (Exception $e) { error_log("Update survey error: " . $e->getMessage()); return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * Delete a survey */ public function deleteSurvey($surveyId, $userId) { try { $sql = "DELETE FROM surveys WHERE id = ? AND created_by = ?"; $stmt = $this->db->prepare($sql); $stmt->bind_param('ii', $surveyId, $userId); if (!$stmt->execute()) { throw new Exception("Failed to delete survey"); } return ['success' => true]; } catch (Exception $e) { error_log("Delete survey error: " . $e->getMessage()); return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * Add or update survey questions */ public function saveQuestion($data) { try { if (isset($data['question_id'])) { // Update existing question $sql = "UPDATE survey_questions SET question_text = ?, question_type = ?, help_text = ?, options = ?, config = ?, is_required = ? WHERE id = ? AND survey_id = ?"; $stmt = $this->db->prepare($sql); $stmt->bind_param('sssssiis', $data['question_text'], $data['question_type'], $data['help_text'], $data['options'], $data['config'], $data['is_required'], $data['question_id'], $data['survey_id'] ); } else { // Add new question $sql = "INSERT INTO survey_questions ( survey_id, question_text, question_type, help_text, options, config, is_required, question_order ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"; $stmt = $this->db->prepare($sql); $stmt->bind_param('isssssii', $data['survey_id'], $data['question_text'], $data['question_type'], $data['help_text'], $data['options'], $data['config'], $data['is_required'], $data['question_order'] ); } if (!$stmt->execute()) { throw new Exception("Failed to save question"); } return [ 'success' => true, 'question_id' => isset($data['question_id']) ? $data['question_id'] : $this->db->getLastInsertId() ]; } catch (Exception $e) { error_log("Save question error: " . $e->getMessage()); return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * Delete a survey question */ public function deleteQuestion($questionId, $surveyId) { try { $sql = "DELETE FROM survey_questions WHERE id = ? AND survey_id = ?"; $stmt = $this->db->prepare($sql); $stmt->bind_param('ii', $questionId, $surveyId); if (!$stmt->execute()) { throw new Exception("Failed to delete question"); } return ['success' => true]; } catch (Exception $e) { error_log("Delete question error: " . $e->getMessage()); return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * Update question order */ public function updateQuestionOrder($surveyId, $questionOrder) { try { $sql = "UPDATE survey_questions SET question_order = ? WHERE id = ? AND survey_id = ?"; $stmt = $this->db->prepare($sql); foreach ($questionOrder as $order => $questionId) { $stmt->bind_param('iii', $order, $questionId, $surveyId); if (!$stmt->execute()) { throw new Exception("Failed to update question order"); } } return ['success' => true]; } catch (Exception $e) { error_log("Update question order error: " . $e->getMessage()); return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * Generate survey token for a panel */ public function generateSurveyToken($surveyId, $sampleId, $selectionId, $expiryHours = 48) { try { $token = bin2hex(random_bytes(32)); $expiryDate = date('Y-m-d H:i:s', strtotime("+{$expiryHours} hours")); $sql = "INSERT INTO survey_tokens ( survey_id, token, sample_id, selection_id, expires_at ) VALUES (?, ?, ?, ?, ?)"; $stmt = $this->db->prepare($sql); $stmt->bind_param('issis', $surveyId, $token, $sampleId, $selectionId, $expiryDate ); if (!$stmt->execute()) { throw new Exception("Failed to generate survey token"); } return [ 'success' => true, 'token' => $token ]; } catch (Exception $e) { error_log("Generate token error: " . $e->getMessage()); return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * Get survey response statistics */ public function getSurveyStats($surveyId) { try { $sql = "SELECT COUNT(DISTINCT st.id) as total_tokens, SUM(st.is_completed) as completed_responses, COUNT(DISTINCT sr.selection_id) as unique_responses, AVG(TIMESTAMPDIFF(MINUTE, st.created_at, st.completed_at)) as avg_completion_time FROM survey_tokens st LEFT JOIN survey_responses sr ON sr.survey_id = st.survey_id WHERE st.survey_id = ?"; $stmt = $this->db->prepare($sql); $stmt->bind_param('i', $surveyId); $stmt->execute(); $stats = $stmt->get_result()->fetch_assoc(); // Get per-question statistics $sql = "SELECT sq.id, sq.question_text, sq.question_type, COUNT(DISTINCT sr.selection_id) as response_count, GROUP_CONCAT(DISTINCT sr.response) as responses FROM survey_questions sq LEFT JOIN survey_responses sr ON sr.question_id = sq.id WHERE sq.survey_id = ? GROUP BY sq.id"; $stmt = $this->db->prepare($sql); $stmt->bind_param('i', $surveyId); $stmt->execute(); $stats['questions'] = $stmt->get_result()->fetch_all(MYSQLI_ASSOC); return $stats; } catch (Exception $e) { error_log("Get survey stats error: " . $e->getMessage()); return null; } } } ?>