158 lines
5.1 KiB
PHP
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;
|