<?php
// Start session
session_start();
// Include database configuration
require_once '../config/database.php';
// Check if user is logged in and has admin role
if (!isset($_SESSION['user_id']) || $_SESSION['role'] !== 'admin') {
header("Location: ../login.php");
exit();
}
// Check if form is submitted
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['bank_id']) || !isset($_FILES['question_file'])) {
$_SESSION['error'] = "Invalid request.";
header("Location: banks.php");
exit();
}
$bank_id = intval($_POST['bank_id']);
// Validate question bank exists
$check_bank = $conn->prepare("SELECT id FROM question_banks WHERE id = ?");
$check_bank->bind_param("i", $bank_id);
$check_bank->execute();
$bank_exists = $check_bank->get_result()->num_rows > 0;
$check_bank->close();
if (!$bank_exists) {
$_SESSION['error'] = "Question bank not found.";
header("Location: banks.php");
exit();
}
// Check file upload
$file = $_FILES['question_file'];
if ($file['error'] !== UPLOAD_ERR_OK) {
$_SESSION['error'] = "Error uploading file: " . getUploadErrorMessage($file['error']);
header("Location: questions.php?bank_id=" . $bank_id);
exit();
}
// Validate file type
$file_ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$allowed_extensions = ['csv', 'xlsx', 'xls'];
if (!in_array($file_ext, $allowed_extensions)) {
$_SESSION['error'] = "Only CSV and Excel files are allowed.";
header("Location: questions.php?bank_id=" . $bank_id);
exit();
}
// Process file based on extension
$questions = [];
$errors = [];
if ($file_ext === 'csv') {
// Process CSV file
$handle = fopen($file['tmp_name'], 'r');
if ($handle !== false) {
// Read header line
$header = fgetcsv($handle);
// Normalize header fields
$header = array_map('trim', $header);
$header = array_map('strtolower', $header);
// Required fields
$required_fields = ['question_text', 'question_type', 'difficulty', 'marks'];
// Check if required fields exist
foreach ($required_fields as $field) {
if (!in_array($field, $header)) {
$errors[] = "Required column '$field' is missing in the CSV file.";
}
}
if (empty($errors)) {
// Get indexes for each column
$column_indexes = [];
foreach ($header as $index => $column_name) {
$column_indexes[$column_name] = $index;
}
// Read data rows
$row_number = 2; // Start from line 2 (after header)
while (($row = fgetcsv($handle)) !== false) {
$question = [
'question_text' => $row[$column_indexes['question_text']] ?? '',
'question_type' => strtolower($row[$column_indexes['question_type']] ?? ''),
'difficulty' => strtolower($row[$column_indexes['difficulty']] ?? ''),
'marks' => floatval($row[$column_indexes['marks']] ?? 0),
'is_practice' => isset($column_indexes['is_practice']) ? (strtolower($row[$column_indexes['is_practice']]) === 'yes' ? 1 : 0) : 0,
'explanation' => $row[$column_indexes['explanation']] ?? '',
'options' => [],
'answer_key' => $row[$column_indexes['answer_key']] ?? '',
'row_number' => $row_number
];
// Add options for multiple choice
if ($question['question_type'] === 'multiple_choice') {
$option_count = 1;
while (isset($column_indexes['option_' . $option_count]) && isset($row[$column_indexes['option_' . $option_count]])) {
$option_text = $row[$column_indexes['option_' . $option_count]];
if (!empty($option_text)) {
$is_correct = isset($column_indexes['correct_option_' . $option_count]) &&
strtolower($row[$column_indexes['correct_option_' . $option_count]]) === 'yes';
$question['options'][] = [
'option_text' => $option_text,
'is_correct' => $is_correct
];
}
$option_count++;
}
}
// Handle true/false questions
elseif ($question['question_type'] === 'true_false') {
$correct_answer = strtolower($row[$column_indexes['correct_answer']] ?? '');
if ($correct_answer === 'true' || $correct_answer === 'false') {
$question['correct_answer'] = $correct_answer;
} else {
$errors[] = "Row $row_number: True/False question must have 'true' or 'false' as correct_answer.";
}
}
// Validate question
validateQuestion($question, $errors);
$questions[] = $question;
$row_number++;
}
}
fclose($handle);
} else {
$errors[] = "Failed to open the CSV file.";
}
} elseif ($file_ext === 'xlsx' || $file_ext === 'xls') {
// For Excel files, we'll need a library like PhpSpreadsheet
// This implementation uses SimpleXLSX as a lightweight alternative
require_once '../vendor/SimpleXLSX.php';
if (!class_exists('SimpleXLSX')) {
$errors[] = "SimpleXLSX library not found. Excel import requires this library.";
} else {
$xlsx = SimpleXLSX::parse($file['tmp_name']);
if ($xlsx) {
$rows = $xlsx->rows();
// First row is the header
$header = array_map('trim', $rows[0]);
$header = array_map('strtolower', $header);
// Required fields
$required_fields = ['question_text', 'question_type', 'difficulty', 'marks'];
// Check if required fields exist
foreach ($required_fields as $field) {
if (!in_array($field, $header)) {
$errors[] = "Required column '$field' is missing in the Excel file.";
}
}
if (empty($errors)) {
// Get indexes for each column
$column_indexes = [];
foreach ($header as $index => $column_name) {
$column_indexes[$column_name] = $index;
}
// Process data rows (skip header)
for ($i = 1; $i < count($rows); $i++) {
$row = $rows[$i];
$row_number = $i + 1;
$question = [
'question_text' => $row[$column_indexes['question_text']] ?? '',
'question_type' => strtolower($row[$column_indexes['question_type']] ?? ''),
'difficulty' => strtolower($row[$column_indexes['difficulty']] ?? ''),
'marks' => floatval($row[$column_indexes['marks']] ?? 0),
'is_practice' => isset($column_indexes['is_practice']) ? (strtolower($row[$column_indexes['is_practice']]) === 'yes' ? 1 : 0) : 0,
'explanation' => $row[$column_indexes['explanation']] ?? '',
'options' => [],
'answer_key' => $row[$column_indexes['answer_key']] ?? '',
'row_number' => $row_number
];
// Add options for multiple choice
if ($question['question_type'] === 'multiple_choice') {
$option_count = 1;
while (isset($column_indexes['option_' . $option_count]) && isset($row[$column_indexes['option_' . $option_count]])) {
$option_text = $row[$column_indexes['option_' . $option_count]];
if (!empty($option_text)) {
$is_correct = isset($column_indexes['correct_option_' . $option_count]) &&
strtolower($row[$column_indexes['correct_option_' . $option_count]]) === 'yes';
$question['options'][] = [
'option_text' => $option_text,
'is_correct' => $is_correct
];
}
$option_count++;
}
}
// Handle true/false questions
elseif ($question['question_type'] === 'true_false') {
$correct_answer = strtolower($row[$column_indexes['correct_answer']] ?? '');
if ($correct_answer === 'true' || $correct_answer === 'false') {
$question['correct_answer'] = $correct_answer;
} else {
$errors[] = "Row $row_number: True/False question must have 'true' or 'false' as correct_answer.";
}
}
// Validate question
validateQuestion($question, $errors);
$questions[] = $question;
}
}
} else {
$errors[] = "Failed to parse Excel file: " . SimpleXLSX::parseError();
}
}
}
// If there are validation errors, redirect back with error message
if (!empty($errors)) {
$_SESSION['error'] = "Import failed. Please fix the following issues:<br>" . implode("<br>", $errors);
header("Location: questions.php?bank_id=" . $bank_id);
exit();
}
// No questions found
if (empty($questions)) {
$_SESSION['error'] = "No valid questions found in the file.";
header("Location: questions.php?bank_id=" . $bank_id);
exit();
}
// Import the questions
$conn->begin_transaction();
$success_count = 0;
$error_count = 0;
try {
foreach ($questions as $question) {
// Insert question
$insert_question = $conn->prepare("INSERT INTO questions (question_bank_id, question_text, question_type, difficulty, marks, is_practice, explanation)
VALUES (?, ?, ?, ?, ?, ?, ?)");
$insert_question->bind_param("isssdis",
$bank_id,
$question['question_text'],
$question['question_type'],
$question['difficulty'],
$question['marks'],
$question['is_practice'],
$question['explanation']
);
$insert_question->execute();
$question_id = $conn->insert_id;
// Insert options based on question type
if ($question['question_type'] === 'multiple_choice') {
foreach ($question['options'] as $option) {
$insert_option = $conn->prepare("INSERT INTO question_options (question_id, option_text, is_correct) VALUES (?, ?, ?)");
$insert_option->bind_param("isi", $question_id, $option['option_text'], $option['is_correct']);
$insert_option->execute();
}
}
elseif ($question['question_type'] === 'true_false') {
// Insert true option
$insert_option = $conn->prepare("INSERT INTO question_options (question_id, option_text, is_correct) VALUES (?, 'True', ?)");
$is_true_correct = ($question['correct_answer'] === 'true') ? 1 : 0;
$insert_option->bind_param("ii", $question_id, $is_true_correct);
$insert_option->execute();
// Insert false option
$insert_option = $conn->prepare("INSERT INTO question_options (question_id, option_text, is_correct) VALUES (?, 'False', ?)");
$is_false_correct = ($question['correct_answer'] === 'false') ? 1 : 0;
$insert_option->bind_param("ii", $question_id, $is_false_correct);
$insert_option->execute();
}
elseif ($question['question_type'] === 'short_answer' && !empty($question['answer_key'])) {
// For short answer, store the answer key
$insert_option = $conn->prepare("INSERT INTO question_options (question_id, option_text, is_correct) VALUES (?, ?, 1)");
$insert_option->bind_param("is", $question_id, $question['answer_key']);
$insert_option->execute();
}
$success_count++;
}
$conn->commit();
$_SESSION['success'] = "$success_count questions imported successfully.";
} catch (Exception $e) {
$conn->rollback();
$_SESSION['error'] = "Error importing questions: " . $e->getMessage();
}
header("Location: questions.php?bank_id=" . $bank_id);
exit();
// Helper functions
function validateQuestion(&$question, &$errors) {
$row_number = $question['row_number'];
// Check required fields
if (empty($question['question_text'])) {
$errors[] = "Row $row_number: Question text is required.";
}
// Validate question type
$valid_types = ['multiple_choice', 'true_false', 'short_answer', 'essay'];
if (!in_array($question['question_type'], $valid_types)) {
$errors[] = "Row $row_number: Invalid question type. Must be one of: " . implode(', ', $valid_types);
}
// Validate difficulty
$valid_difficulties = ['easy', 'medium', 'hard'];
if (!in_array($question['difficulty'], $valid_difficulties)) {
$errors[] = "Row $row_number: Invalid difficulty. Must be one of: " . implode(', ', $valid_difficulties);
}
// Validate marks
if ($question['marks'] <= 0) {
$errors[] = "Row $row_number: Marks must be greater than 0.";
}
// Validate question type specific requirements
if ($question['question_type'] === 'multiple_choice') {
// Check if options exist
if (empty($question['options'])) {
$errors[] = "Row $row_number: Multiple choice questions must have options.";
} else {
// Check if at least one option is marked as correct
$has_correct = false;
foreach ($question['options'] as $option) {
if ($option['is_correct']) {
$has_correct = true;
break;
}
}
if (!$has_correct) {
$errors[] = "Row $row_number: Multiple choice questions must have at least one correct option.";
}
}
} elseif ($question['question_type'] === 'short_answer') {
// Short answer questions should have an answer key
if (empty($question['answer_key'])) {
$errors[] = "Row $row_number: Short answer questions must have an answer key.";
}
}
}
function getUploadErrorMessage($error_code) {
switch ($error_code) {
case UPLOAD_ERR_INI_SIZE:
return "The uploaded file exceeds the upload_max_filesize directive in php.ini.";
case UPLOAD_ERR_FORM_SIZE:
return "The uploaded file exceeds the MAX_FILE_SIZE directive in the HTML form.";
case UPLOAD_ERR_PARTIAL:
return "The uploaded file was only partially uploaded.";
case UPLOAD_ERR_NO_FILE:
return "No file was uploaded.";
case UPLOAD_ERR_NO_TMP_DIR:
return "Missing a temporary folder.";
case UPLOAD_ERR_CANT_WRITE:
return "Failed to write file to disk.";
case UPLOAD_ERR_EXTENSION:
return "A PHP extension stopped the file upload.";
default:
return "Unknown upload error.";
}
}