36459-vm/submit_solution.php
2025-11-29 17:28:26 +00:00

158 lines
5.1 KiB
PHP

<?php
include 'includes/header.php';
require_once 'db/config.php';
// --- Security & Utility Functions ---
/**
* Executes user-submitted Python code in a sandboxed environment.
*
* WARNING: This is a placeholder and is NOT secure for a production environment.
* It uses shell_exec, which is highly insecure. This MUST be replaced with a
* proper sandboxed execution engine (e.g., Docker containers, r-jail, etc.).
*
* @param string $solution The Python code to execute.
* @return string The output from the executed code.
*/
function execute_python_safely(string $solution): string
{
// Create a temporary file to hold the user's code
$temp_file = tempnam(sys_get_temp_dir(), 'user_solution_') . '.py';
file_put_contents($temp_file, $solution);
// Construct the command to execute the Python script.
// The `2>&1` redirects stderr to stdout to capture any errors.
$command = 'python3 ' . escapeshellarg($temp_file) . ' 2>&1';
// Execute the command
$output = shell_exec($command);
// Clean up the temporary file
unlink($temp_file);
return $output ?? '';
}
/**
* Compares the actual output with the expected output, ignoring whitespace differences.
*
* @param string $actualOutput The output from the user's code.
* @param string $expectedOutput The correct output from the database.
* @return bool True if the outputs match, false otherwise.
*/
function compare_outputs(string $actualOutput, string $expectedOutput): bool
{
return trim($actualOutput) === trim($expectedOutput);
}
/**
* Awards a specific badge to a user if they don't already have it.
*
* @param PDO $pdo The database connection.
* @param int $user_id The user's ID.
* @param string $badgeName The name of the badge to award.
*/
function award_badge(PDO $pdo, int $user_id, string $badgeName): void
{
// Find the badge ID
$stmt = $pdo->prepare('SELECT id FROM badges WHERE name = ?');
$stmt->execute([$badgeName]);
$badge = $stmt->fetch();
if ($badge) {
// Insert the badge for the user, ignoring duplicates
$stmt = $pdo->prepare('INSERT IGNORE INTO user_badges (user_id, badge_id) VALUES (?, ?)');
$stmt->execute([$user_id, $badge['id']]);
}
}
// --- Main Logic ---
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit;
}
$user_id = $_SESSION['user_id'];
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['challenge_id'], $_POST['solution'])) {
header('Location: challenges.php');
exit;
}
$challenge_id = $_POST['challenge_id'];
$solution = $_POST['solution'];
$language = $_POST['language'] ?? 'python'; // Default to python
if (empty($solution)) {
$_SESSION['error_message'] = 'Please provide a solution.';
header('Location: challenge.php?id=' . $challenge_id);
exit;
}
$pdo = db();
// Get challenge details
$stmt = $pdo->prepare('SELECT name, expected_output FROM challenges WHERE id = ?');
$stmt->execute([$challenge_id]);
$challenge = $stmt->fetch();
if (!$challenge) {
header('Location: challenges.php');
exit;
}
// --- Grading ---
$status = 'pending';
if ($language === 'python') {
$actual_output = execute_python_safely($solution);
if (compare_outputs($actual_output, $challenge['expected_output'])) {
$status = 'passed';
} else {
$status = 'failed';
}
}
// --- Record Submission ---
$stmt = $pdo->prepare('
INSERT INTO challenge_submissions (user_id, challenge_id, language, submission_content, status)
VALUES (?, ?, ?, ?, ?)
');
$stmt->execute([$user_id, $challenge_id, $language, $solution, $status]);
// --- Post-Submission Actions ---
if ($status === 'passed') {
$_SESSION['success_message'] = "Correct solution for '{$challenge['name']}'! Well done!";
// Award "First Challenge Completed" badge if it's their first
$stmt = $pdo->prepare('SELECT COUNT(*) as passed_count FROM challenge_submissions WHERE user_id = ? AND status = 'passed'');
$stmt->execute([$user_id]);
$result = $stmt->fetch();
if ($result && $result['passed_count'] === 1) {
award_badge($pdo, $user_id, 'First Challenge Completed');
$_SESSION['success_message'] .= ' You just earned the "First Challenge Completed" badge!';
}
// Send congratulatory email
$stmt = $pdo->prepare('SELECT name, email FROM users WHERE id = ?');
$stmt->execute([$user_id]);
$user = $stmt->fetch();
if ($user) {
require_once 'mail/MailService.php';
$subject = 'Congratulations on completing a challenge!';
$body = "Hello {$user['name']},<br><br>You have successfully completed the '{$challenge['name']}' challenge. Keep up the great work!<br><br>You can view any new badges or certificates in your profile.<br><br>Best regards,<br>The RS Learning Lab Team";
MailService::sendMail($user['email'], $subject, $body, strip_tags($body));
}
} elseif ($status === 'failed') {
$_SESSION['error_message'] = "Your solution for '{$challenge['name']}' is incorrect. Try again!";
} else {
$_SESSION['info_message'] = 'Your solution has been submitted successfully and is pending review.';
}
header('Location: challenges.php');
exit;