📄 Source: create-forum.php
<?php
require_once 'includes/config.php';
require_once 'includes/db.php';
require_once 'includes/auth.php';
require_once 'includes/functions.php';
// Anyone logged in can create a forum
requireLogin();
$user = getCurrentUser();
$error = '';
$success = '';
$forumId = '';
$newForum = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$title = sanitize($_POST['title'] ?? '');
$description = sanitize($_POST['description'] ?? '');
$platform = sanitize($_POST['platform'] ?? 'none');
$timeLimit = validateTimeLimit($_POST['time_limit'] ?? DEFAULT_TIME_LIMIT);
$questions = [];
$questionTexts = $_POST['question_text'] ?? [];
$questionTypes = $_POST['question_type'] ?? [];
$questionRequired = $_POST['question_required'] ?? [];
$questionMaxChars = $_POST['question_max_chars'] ?? [];
$optionsData = $_POST['options'] ?? [];
$correctAnswers = $_POST['correct_answer'] ?? [];
for ($i = 0; $i < count($questionTexts); $i++) {
if (!empty($questionTexts[$i])) {
$q = [
'text' => sanitize($questionTexts[$i]),
'type' => $questionTypes[$i] ?? 'text',
'required' => isset($questionRequired[$i]) ? true : false,
'max_chars' => intval($questionMaxChars[$i] ?? getMaxChars($questionTypes[$i] ?? 'text'))
];
// Only add correct answers for choice/checkbox questions
if ($q['type'] === 'choice' || $q['type'] === 'checkbox') {
$opts = isset($optionsData[$i]) ? array_values(array_filter($optionsData[$i], function($opt) {
return !empty(trim($opt));
})) : [];
$q['options'] = array_map('sanitize', $opts);
if ($q['type'] === 'choice') {
$q['correct'] = isset($correctAnswers[$i]) ? $correctAnswers[$i] : '';
} else {
$q['correct'] = isset($correctAnswers[$i]) ? (is_array($correctAnswers[$i]) ? $correctAnswers[$i] : [$correctAnswers[$i]]) : [];
}
// In the POST handling section, add this for role questions:
if ($q['type'] === 'roles') {
// Get selected roles from the form
$selectedRoles = $_POST['selected_roles'][$i] ?? [];
if (is_array($selectedRoles)) {
$q['correct'] = $selectedRoles; // Store as array of role names
}
// Options will be populated from Discord server roles
$q['options'] = $_POST['discord_roles'] ?? []; // Will be populated from server
}
} else {
// Text/Paragraph questions don't have correct answers
$q['correct'] = null;
$q['options'] = null;
}
$questions[] = $q;
}
}
if (empty($title)) {
$error = 'Please enter a forum title';
} elseif (empty($questions)) {
$error = 'Please add at least one question';
} else {
$forumData = [
'title' => $title,
'description' => $description,
'platform' => $platform,
'time_limit' => $timeLimit,
'questions' => $questions,
'created_by' => $user['id'],
'status' => 'open',
'admins' => [$user['id']] // Creator is automatically an admin
];
$forumId = createForum($forumData);
if ($forumId) {
$newForum = getForum($forumId);
$success = 'Forum created successfully!';
} else {
$error = 'Failed to create forum';
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Create Forum - <?php echo SITE_NAME; ?></title>
<link href="https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,300;400;500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/style.css">
<style>
.question-builder {
background: var(--bg-card);
border-radius: 1rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
border: 1px solid var(--border-color);
}
.question-builder .question-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
flex-wrap: wrap;
gap: 0.5rem;
}
.option-row {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
background: var(--option-bg);
padding: 0.4rem 0.4rem 0.4rem 1rem;
border-radius: 2rem;
border: 1px solid var(--border-color);
}
.option-row input {
flex: 1;
background: transparent;
border: none;
padding: 0.5rem;
color: var(--input-text);
}
.option-row input:focus {
outline: none;
}
.remove-option {
background: var(--error);
color: white;
border: none;
border-radius: 2rem;
width: 28px;
height: 28px;
cursor: pointer;
}
.correct-badge {
cursor: pointer;
padding: 0.25rem 0.7rem;
border-radius: 2rem;
background: var(--input-bg);
border: 1px solid var(--border-color);
font-size: 0.7rem;
white-space: nowrap;
}
.correct-badge.selected {
background: var(--success);
color: white;
}
.question-type-select {
padding: 0.5rem;
border-radius: 0.5rem;
border: 1px solid var(--border-color);
background: var(--input-bg);
color: var(--input-text);
}
.checkbox-group {
display: flex;
align-items: center;
gap: 0.5rem;
margin: 0.5rem 0;
}
.checkbox-group input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
}
.char-limit {
font-size: 0.8rem;
color: var(--text-secondary);
}
.admin-list {
margin-top: 1rem;
}
.admin-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem;
background: var(--code-bg);
border-radius: 0.5rem;
margin-bottom: 0.3rem;
}
.admin-item .owner-badge {
font-size: 0.7rem;
background: var(--warning);
color: white;
padding: 0.1rem 0.5rem;
border-radius: 2rem;
}
.admin-item .remove-admin {
color: var(--error);
text-decoration: none;
font-size: 0.8rem;
}
.admin-item .remove-admin:hover {
text-decoration: underline;
}
.no-correct-needed {
font-size: 0.8rem;
color: var(--text-secondary);
font-style: italic;
margin-top: 0.3rem;
}
.btn-secondary {
background: var(--btn-success);
color: white;
border: none;
padding: 0.5rem 1.2rem;
border-radius: 2rem;
cursor: pointer;
font-size: 0.8rem;
font-weight: 500;
transition: all 0.2s;
}
.btn-secondary:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}
</style>
</head>
<body>
<div class="theme-switcher">
<button class="theme-btn" data-theme="light" title="Light mode">☀️</button>
<button class="theme-btn" data-theme="dark" title="Dark mode">🌙</button>
</div>
<div class="container">
<header class="header">
<div class="header-top">
<h1>📝 Create Application Forum</h1>
<a href="index.php" class="back-link">← Back</a>
</div>
<p class="subhead">Set up a new application form with custom questions</p>
</header>
<?php if ($success): ?>
<div class="success-message">
✅ <?php echo htmlspecialchars($success); ?>
<br>
<a href="apply.php?id=<?php echo $forumId; ?>" style="color: white; text-decoration: underline;">View your forum</a>
<br>
<a href="dashboard.php?forum_id=<?php echo $forumId; ?>" style="color: white; text-decoration: underline;">Manage your forum</a>
</div>
<!-- Admin Management Section -->
<?php if ($newForum): ?>
<div class="form-card">
<h3>👔 Forum Admins</h3>
<p style="color: var(--text-secondary); font-size: 0.9rem;">Add users who can review and manage applications for this forum</p>
<form method="POST" action="manage-admins.php" style="display: flex; gap: 0.5rem; margin: 1rem 0; flex-wrap: wrap;">
<input type="hidden" name="forum_id" value="<?php echo $forumId; ?>">
<input type="hidden" name="action" value="add_admin">
<div style="flex: 1; min-width: 200px;">
<input type="text" name="username" placeholder="Enter username to add as admin" style="width: 100%; padding: 0.9rem; border-radius: 1rem; border: 2px solid var(--input-border); background: var(--input-bg); color: var(--input-text);">
</div>
<button type="submit" class="btn-primary" style="padding: 0.9rem 1.5rem;">Add Admin</button>
</form>
<div class="admin-list">
<strong>Current Admins:</strong>
<?php foreach ($newForum['admins'] as $adminId): ?>
<?php $admin = getUser($adminId); ?>
<?php if ($admin): ?>
<div class="admin-item">
<span>
<?php echo htmlspecialchars($admin['username']); ?>
<?php if ($adminId === $newForum['created_by']): ?>
<span class="owner-badge">👑 Owner</span>
<?php endif; ?>
</span>
<?php if ($adminId !== $newForum['created_by']): ?>
<a href="manage-admins.php?forum_id=<?php echo $forumId; ?>&action=remove&user_id=<?php echo $adminId; ?>"
onclick="return confirm('Remove this admin?')"
class="remove-admin">Remove</a>
<?php endif; ?>
</div>
<?php endif; ?>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<?php endif; ?>
<?php if ($error): ?>
<div class="error-message">⚠️ <?php echo htmlspecialchars($error); ?></div>
<?php endif; ?>
<?php if (!$success): ?>
<form method="POST" action="" id="forumForm">
<div class="form-card">
<div class="input-field">
<label>📌 Forum Title *</label>
<input type="text" name="title" placeholder="e.g., Staff Application - Server Name" required>
</div>
<div class="input-field">
<label>📝 Description</label>
<textarea name="description" rows="3" placeholder="Optional description about this application forum" style="width: 100%; padding: 0.9rem; border-radius: 1rem; border: 2px solid var(--input-border); background: var(--input-bg); color: var(--input-text); font-family: inherit;"></textarea>
</div>
<div class="input-field">
<label>🔐 Platform Requirement</label>
<select name="platform" style="width: 100%; padding: 0.9rem; border-radius: 1rem; border: 2px solid var(--input-border); background: var(--input-bg); color: var(--input-text);">
<option value="none">None (Manual entry)</option>
<option value="discord">Discord</option>
<option value="google">Google</option>
<!-- <option value="roblox">Roblox</option> -->
</select>
<small style="color: var(--text-secondary);">Users will need to login with this platform to apply</small>
</div>
<?php if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !$success): ?>
<div class="input-field" id="discordServerField" style="display: none;">
<label>🟣 Discord Server</label>
<select name="discord_server_id" style="width: 100%; padding: 0.9rem; border-radius: 1rem; border: 2px solid var(--input-border); background: var(--input-bg); color: var(--input-text);">
<option value="">Select a server...</option>
<!-- This will be populated by the bot later -->
<option value="server1">Server Name 1</option>
<option value="server2">Server Name 2</option>
</select>
<small style="color: var(--text-secondary);">Select the Discord server this forum is for (requires bot)</small>
</div>
<div class="input-field" id="discordRolesField" style="display: none;">
<label>🎭 Available Roles</label>
<div id="rolesContainer" style="display: flex; flex-wrap: wrap; gap: 0.5rem; padding: 0.5rem; background: var(--code-bg); border-radius: 0.5rem; min-height: 50px;">
<span style="color: var(--text-secondary); font-size: 0.9rem;">Select a server to load roles...</span>
</div>
<small style="color: var(--text-secondary);">These roles will be available for role selection questions</small>
</div>
<script>
document.querySelector('select[name="platform"]').addEventListener('change', function() {
const discordField = document.getElementById('discordServerField');
const rolesField = document.getElementById('discordRolesField');
if (this.value === 'discord') {
discordField.style.display = 'block';
rolesField.style.display = 'block';
} else {
discordField.style.display = 'none';
rolesField.style.display = 'none';
}
});
</script>
<?php endif; ?>
<div class="input-field">
<label>⏱️ Time Limit</label>
<input type="number" name="time_limit" value="60" min="5" max="180" style="width: 100%; padding: 0.9rem; border-radius: 1rem; border: 2px solid var(--input-border); background: var(--input-bg); color: var(--input-text);">
<small style="color: var(--text-secondary);">Minutes to complete the application (5-180)</small>
</div>
</div>
<h3>📋 Questions</h3>
<div id="questionsContainer"></div>
<button type="button" class="btn-secondary" onclick="addQuestion()" style="margin: 1rem 0; width: 100%; padding: 0.8rem;">
➕ Add Question
</button>
<button type="submit" class="btn-primary" style="width: 100%; background: var(--btn-success);">
🚀 Create Forum
</button>
</form>
<?php endif; ?>
<footer class="footer">
<p>💡 Click on ✓ to mark correct answers for multiple choice questions (not needed for text/paragraph questions)</p>
</footer>
</div>
<script>
let questionCounter = 0;
function addQuestion() {
const container = document.getElementById('questionsContainer');
const qid = questionCounter;
const div = document.createElement('div');
div.className = 'question-builder';
div.setAttribute('data-qid', qid);
div.innerHTML = `
<div class="question-header">
<strong>Question ${questionCounter + 1}</strong>
<button type="button" class="btn-danger" onclick="removeQuestion(this)">✖ Remove</button>
</div>
<div class="input-field">
<input type="text" name="question_text[]" placeholder="Enter your question" required style="width: 100%; padding: 0.9rem; border-radius: 1rem; border: 2px solid var(--input-border); background: var(--input-bg); color: var(--input-text);">
</div>
<div style="display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 1rem;">
<div style="flex: 1; min-width: 150px;">
<label style="font-size: 0.8rem; font-weight: 600;">Question Type</label>
<select name="question_type[]" class="question-type-select" onchange="toggleQuestionType(this)" style="width: 100%; padding: 0.5rem; border-radius: 0.5rem; border: 1px solid var(--border-color); background: var(--input-bg); color: var(--input-text);">
<option value="text">📝 Short Text</option>
<option value="textarea">📄 Paragraph</option>
<option value="choice">🔘 Multiple Choice</option>
<option value="checkbox">☑️ Checkboxes</option>
<option value="roles">🎭 Role Selection</option>
</select>
</div>
<div style="flex: 1; min-width: 100px;">
<label style="font-size: 0.8rem; font-weight: 600;">Max Characters</label>
<input type="number" name="question_max_chars[]" value="2048" min="1" max="4096" style="width: 100%; padding: 0.5rem; border-radius: 0.5rem; border: 1px solid var(--border-color); background: var(--input-bg); color: var(--input-text);">
</div>
<div style="display: flex; align-items: center;">
<div class="checkbox-group">
<input type="checkbox" name="question_required[]" checked>
<label style="font-size: 0.8rem; font-weight: 600;">Required</label>
</div>
</div>
</div>
<!-- Options Section (for choice/checkbox questions) -->
<div class="options-section" style="display: none;">
<label style="font-weight: 600; font-size: 0.9rem;">📋 Options:</label>
<div id="options-list-${qid}" style="margin: 0.5rem 0;"></div>
<button type="button" class="btn-secondary" onclick="addOption(${qid})" style="margin-top: 0.3rem; padding: 0.5rem 1.2rem; background: var(--btn-success); color: white; border: none; border-radius: 2rem; cursor: pointer; font-size: 0.8rem; font-weight: 500; transition: all 0.2s;">
➕ Add Option
</button>
<div style="margin-top: 0.5rem;">
<label style="font-size: 0.8rem; font-weight: 600;">✅ Correct Answer(s):</label>
<div id="correct-display-${qid}" style="font-size: 0.8rem; color: var(--success); margin-top: 0.3rem;">Click ✓ on options to mark as correct</div>
</div>
</div>
<!-- Role Selection Section -->
<div class="roles-section" style="display: none;">
<label style="font-weight: 600; font-size: 0.9rem;">🎭 Select Roles:</label>
<div id="roles-list-${qid}" style="margin: 0.5rem 0; display: flex; flex-wrap: wrap; gap: 0.5rem;">
<span style="color: var(--text-secondary); font-size: 0.9rem;">Select a Discord server in forum settings to load roles</span>
</div>
<div style="margin-top: 0.5rem;">
<label style="font-size: 0.8rem; font-weight: 600;">✅ Selected Roles (multiple allowed):</label>
<div id="roles-display-${qid}" style="font-size: 0.8rem; color: var(--success); margin-top: 0.3rem;">Click roles above to select them</div>
</div>
</div>
<!-- Info for text/paragraph questions -->
<div class="no-correct-needed" style="display: block; font-size: 0.8rem; color: var(--text-secondary); font-style: italic; margin-top: 0.3rem;">
💡 Text/Paragraph questions don't need correct answers - they're open-ended responses
</div>
`;
container.appendChild(div);
// Add default options for choice/checkbox questions
for (let i = 0; i < 2; i++) {
addOption(qid);
}
// Add default roles (placeholder - will be loaded from Discord)
const rolesList = document.getElementById(`roles-list-${qid}`);
if (rolesList) {
rolesList.innerHTML = `
<span style="color: var(--text-secondary); font-size: 0.9rem;">No Discord server selected. Please select a server in forum settings.</span>
`;
}
questionCounter++;
}
function removeQuestion(btn) {
const card = btn.closest('.question-builder');
if (document.querySelectorAll('.question-builder').length > 1) {
card.remove();
// Renumber
document.querySelectorAll('.question-builder').forEach((card, idx) => {
const header = card.querySelector('.question-header strong');
if (header) header.textContent = `Question ${idx + 1}`;
card.setAttribute('data-qid', idx);
});
} else {
alert('You need at least one question!');
}
}
function toggleQuestionType(select) {
const card = select.closest('.question-builder');
const optionsSection = card.querySelector('.options-section');
const rolesSection = card.querySelector('.roles-section');
const noCorrectNeeded = card.querySelector('.no-correct-needed');
const qid = card.getAttribute('data-qid');
// Hide all sections first
if (optionsSection) optionsSection.style.display = 'none';
if (rolesSection) rolesSection.style.display = 'none';
if (noCorrectNeeded) noCorrectNeeded.style.display = 'none';
// Show the relevant section
if (select.value === 'choice' || select.value === 'checkbox') {
if (optionsSection) optionsSection.style.display = 'block';
// Populate options if empty
const optionsList = document.getElementById(`options-list-${qid}`);
if (optionsList && optionsList.children.length === 0) {
for (let i = 0; i < 2; i++) {
addOption(parseInt(qid));
}
}
} else if (select.value === 'roles') {
if (rolesSection) rolesSection.style.display = 'block';
// Try to load roles from Discord server
loadDiscordRoles(qid);
} else {
// Text or textarea
if (noCorrectNeeded) noCorrectNeeded.style.display = 'block';
// Clear any selected correct answers
const badges = card.querySelectorAll('.correct-badge');
badges.forEach(badge => {
badge.classList.remove('selected');
badge.textContent = '✓';
});
const display = document.getElementById(`correct-display-${qid}`);
if (display) {
display.innerHTML = 'Click ✓ on options to mark as correct';
display.style.color = 'var(--text-secondary)';
}
}
}
// Track selected roles per question
let selectedRoles = {};
function toggleRoleSelection(qid, element, roleName) {
if (!selectedRoles[qid]) {
selectedRoles[qid] = [];
}
const index = selectedRoles[qid].indexOf(roleName);
if (index > -1) {
selectedRoles[qid].splice(index, 1);
element.classList.remove('selected');
element.style.background = 'var(--option-bg)';
element.style.color = 'var(--text-primary)';
} else {
selectedRoles[qid].push(roleName);
element.classList.add('selected');
element.style.background = 'var(--info)';
element.style.color = 'white';
}
// Update display
const display = document.getElementById(`roles-display-${qid}`);
if (display) {
if (selectedRoles[qid].length > 0) {
display.innerHTML = `✅ Selected: ${selectedRoles[qid].join(', ')}`;
display.style.color = 'var(--success)';
} else {
display.innerHTML = 'Click roles above to select them';
display.style.color = 'var(--text-secondary)';
}
}
}
function loadDiscordRoles(qid) {
const rolesList = document.getElementById(`roles-list-${qid}`);
if (!rolesList) return;
// Check if a Discord server is selected in the forum settings
const serverSelect = document.querySelector('select[name="discord_server_id"]');
if (!serverSelect || !serverSelect.value) {
rolesList.innerHTML = `
<span style="color: var(--text-secondary); font-size: 0.9rem;">
⚠️ Please select a Discord server in the forum settings above
</span>
`;
return;
}
// Show loading state
rolesList.innerHTML = `
<span style="color: var(--text-secondary); font-size: 0.9rem;">Loading roles from Discord...</span>
`;
// TODO: Fetch roles from Discord API using the bot
// For now, show placeholder roles
setTimeout(() => {
const placeholderRoles = ['@Admin', '@Moderator', '@Helper', '@Member', '@Builder'];
rolesList.innerHTML = placeholderRoles.map(role => `
<button type="button" class="role-btn" onclick="toggleRoleSelection(${qid}, this, '${role}')"
style="background: var(--option-bg); border: 2px solid var(--border-color); border-radius: 2rem; padding: 0.4rem 1rem; cursor: pointer; transition: all 0.2s; color: var(--text-primary);">
${role}
</button>
`).join('');
}, 500);
}
function addOption(qid) {
const container = document.getElementById(`options-list-${qid}`);
if (!container) return;
const optIndex = container.children.length;
const div = document.createElement('div');
div.className = 'option-row';
div.innerHTML = `
<input type="text" name="options[${qid}][]" placeholder="Option ${optIndex + 1}" style="flex: 1; background: transparent; border: none; padding: 0.5rem; color: var(--input-text); font-size: 0.9rem;">
<span class="correct-badge" onclick="toggleCorrectOption(${qid}, this)" style="cursor: pointer; padding: 0.25rem 0.7rem; border-radius: 2rem; background: var(--input-bg); border: 1px solid var(--border-color); font-size: 0.7rem; white-space: nowrap;">✓</span>
<button type="button" class="remove-option" onclick="this.closest('.option-row').remove()" style="background: var(--btn-danger); color: white; border: none; border-radius: 2rem; width: 28px; height: 28px; cursor: pointer; font-size: 0.8rem;">✖</button>
`;
container.appendChild(div);
}
function toggleCorrectOption(qid, element) {
const card = document.querySelector(`.question-builder[data-qid="${qid}"]`);
const typeSelect = card.querySelector('.question-type-select');
const isCheckbox = typeSelect.value === 'checkbox';
const optionInput = element.closest('.option-row').querySelector('input');
const optionText = optionInput.value;
if (!optionText.trim()) {
alert('Please enter the option text first!');
return;
}
if (isCheckbox) {
// Multiple correct allowed
if (element.classList.contains('selected')) {
element.classList.remove('selected');
element.textContent = '✓';
} else {
element.classList.add('selected');
element.textContent = '✓✓';
}
updateCorrectDisplay(qid);
} else {
// Single correct
const allBadges = card.querySelectorAll('.correct-badge');
allBadges.forEach(badge => {
badge.classList.remove('selected');
badge.textContent = '✓';
});
element.classList.add('selected');
element.textContent = '✓ SELECTED';
updateCorrectDisplay(qid);
}
}
function updateCorrectDisplay(qid) {
const card = document.querySelector(`.question-builder[data-qid="${qid}"]`);
const display = document.getElementById(`correct-display-${qid}`);
if (!display) return;
const selectedBadges = card.querySelectorAll('.correct-badge.selected');
const selectedTexts = [];
selectedBadges.forEach(badge => {
const input = badge.closest('.option-row').querySelector('input');
if (input && input.value.trim()) {
selectedTexts.push(input.value.trim());
}
});
if (selectedTexts.length > 0) {
display.innerHTML = `✅ Correct: ${selectedTexts.join(', ')}`;
display.style.color = 'var(--success)';
} else {
display.innerHTML = 'Click ✓ on options to mark as correct';
display.style.color = 'var(--text-secondary)';
}
}
// Initialize with one question
addQuestion();
</script>
<script src="assets/script.js"></script>
</body>
</html>
← Back