diff --git a/api/add_officer.php b/api/add_officer.php new file mode 100644 index 0000000..afb274f --- /dev/null +++ b/api/add_officer.php @@ -0,0 +1,75 @@ +beginTransaction(); + + // 1. Check if user already exists + $stmt = $pdo->prepare("SELECT id FROM users WHERE student_id = ? OR email = ?"); + $stmt->execute([$student_id, $email]); + $existing = $stmt->fetch(); + + if ($existing) { + $user_id = $existing["id"]; + // Update role if changed + $upd = $pdo->prepare("UPDATE users SET role = ? WHERE id = ?"); + $upd->execute([$role, $user_id]); + } else { + // 1a. Create user in Supabase + $supabaseUser = SupabaseAuth::createUser($email, $password); + $supabase_uid = null; + if ($supabaseUser['error']) { + if (str_contains(strtolower($supabaseUser['error']), 'already registered')) { + $sbUser = SupabaseAuth::getUserByEmail($email); + $supabase_uid = $sbUser['id'] ?? null; + } else { + throw new Exception("Supabase Error: " . $supabaseUser['error']); + } + } else { + $supabase_uid = $supabaseUser['data']['id'] ?? null; + } + + // Create new user locally + $user_id = uuid(); + $stmt = $pdo->prepare("INSERT INTO users (id, supabase_uid, student_id, name, email, role) VALUES (?, ?, ?, ?, ?, ?)"); + $stmt->execute([$user_id, $supabase_uid, $student_id, $name, $email, $role]); + } + + // 2. Assign to election + $chk = $pdo->prepare("SELECT COUNT(*) FROM election_assignments WHERE election_id = ? AND user_id = ?"); + $chk->execute([$election_id, $user_id]); + if ($chk->fetchColumn() == 0) { + $role_in_election = $role; // Admin, Adviser, or Officer + $ea = $pdo->prepare("INSERT INTO election_assignments (id, election_id, user_id, role_in_election, assigned_by) VALUES (?, ?, ?, ?, ?)"); + $ea->execute([uuid(), $election_id, $user_id, $role_in_election, $_SESSION['user_id']]); + } + + audit_log('assigned_officer', 'users', $user_id, null, null, "Assigned $role $name to election $election_id"); + + $pdo->commit(); + header("Location: ../officers_management.php?success=officer_assigned"); + exit; + } catch (Exception $e) { + if (isset($pdo) && $pdo->inTransaction()) $pdo->rollBack(); + die("Error: " . $e->getMessage()); + } +} else { + header("Location: ../officers_management.php"); + exit; +} diff --git a/api/add_position.php b/api/add_position.php index 996dec9..8fb3b88 100644 --- a/api/add_position.php +++ b/api/add_position.php @@ -7,7 +7,7 @@ require_role(['Admin', 'Adviser', 'Officer']); if ($_SERVER['REQUEST_METHOD'] === 'POST') { $election_id = $_POST['election_id'] ?? ''; $name = $_POST['name'] ?? ''; - $max_votes = (int)($_POST['max_votes'] ?? 1); + $type = $_POST['type'] ?? 'Uniform'; if (!$election_id || !$name) { die("Missing fields"); @@ -16,8 +16,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { try { $pdo = db(); $id = uuid(); - $stmt = $pdo->prepare("INSERT INTO positions (id, election_id, name, max_votes) VALUES (?, ?, ?, ?)"); - $stmt->execute([$id, $election_id, $name, $max_votes]); + $stmt = $pdo->prepare("INSERT INTO positions (id, election_id, name, type, max_votes) VALUES (?, ?, ?, ?, 1)"); + $stmt->execute([$id, $election_id, $name, $type]); audit_log('Added position', 'positions', $id); diff --git a/api/add_voter.php b/api/add_voter.php new file mode 100644 index 0000000..bb6e2bf --- /dev/null +++ b/api/add_voter.php @@ -0,0 +1,73 @@ +beginTransaction(); + + // 1. Check if user already exists + $stmt = $pdo->prepare("SELECT id FROM users WHERE student_id = ? OR email = ?"); + $stmt->execute([$student_id, $email]); + $existing = $stmt->fetch(); + + if ($existing) { + $user_id = $existing["id"]; + // Update track/grade if needed + $upd = $pdo->prepare("UPDATE users SET track = ?, grade_level = ? WHERE id = ?"); + $upd->execute([$track, $grade_level, $user_id]); + } else { + // 1a. Create user in Supabase + $supabaseUser = SupabaseAuth::createUser($email, $password); + $supabase_uid = null; + if ($supabaseUser['error']) { + // If user already exists in Supabase, try to get their UID + if (str_contains(strtolower($supabaseUser['error']), 'already registered')) { + $sbUser = SupabaseAuth::getUserByEmail($email); + $supabase_uid = $sbUser['id'] ?? null; + } else { + throw new Exception("Supabase Error: " . $supabaseUser['error']); + } + } else { + $supabase_uid = $supabaseUser['data']['id'] ?? null; + } + + // Create new user locally + $user_id = uuid(); + $stmt = $pdo->prepare("INSERT INTO users (id, supabase_uid, student_id, name, email, track, grade_level, role) VALUES (?, ?, ?, ?, ?, ?, ?, 'Voter')"); + $stmt->execute([$user_id, $supabase_uid, $student_id, $name, $email, $track, $grade_level]); + } + + // 2. Assign to election + $chk = $pdo->prepare("SELECT COUNT(*) FROM election_assignments WHERE election_id = ? AND user_id = ?"); + $chk->execute([$election_id, $user_id]); + if ($chk->fetchColumn() == 0) { + $ea = $pdo->prepare("INSERT INTO election_assignments (id, election_id, user_id, role_in_election, assigned_by) VALUES (?, ?, ?, 'Voter', ?)"); + $ea->execute([uuid(), $election_id, $user_id, $_SESSION['user_id']]); + } + + audit_log("Registered voter", "users", $user_id); + + $pdo->commit(); + header("Location: ../voter_management.php?success=voter_added"); + exit; + } catch (Exception $e) { + if (isset($pdo) && $pdo->inTransaction()) $pdo->rollBack(); + die("Error: " . $e->getMessage()); + } +} diff --git a/api/delete_candidate.php b/api/delete_candidate.php new file mode 100644 index 0000000..d948523 --- /dev/null +++ b/api/delete_candidate.php @@ -0,0 +1,24 @@ +prepare("DELETE FROM candidates WHERE id = ?"); + $stmt->execute([$candId]); + + $currentUser = get_user(); + audit_log('candidate_deleted', 'candidates', $candId, null, null, "Deleted candidate ID $candId"); + + header("Location: ../candidate_management.php?success=candidate_deleted"); + exit; + } catch (PDOException $e) { + die("Error deleting candidate: " . $e->getMessage()); + } +} else { + header("Location: ../candidate_management.php"); + exit; +} diff --git a/api/delete_officer.php b/api/delete_officer.php new file mode 100644 index 0000000..39ddf5f --- /dev/null +++ b/api/delete_officer.php @@ -0,0 +1,26 @@ +prepare("DELETE FROM election_assignments WHERE user_id = ? AND election_id = ? AND role_in_election != 'Voter'"); + $stmt->execute([$userId, $electionId]); + + $currentUser = get_user(); + audit_log('officer_removed', 'users', $userId, null, null, "Removed officer ID $userId from election $electionId"); + + header("Location: ../officers_management.php?success=officer_deleted"); + exit; + } catch (PDOException $e) { + die("Error deleting officer: " . $e->getMessage()); + } +} else { + header("Location: ../officers_management.php"); + exit; +} diff --git a/api/delete_voter.php b/api/delete_voter.php new file mode 100644 index 0000000..8e23107 --- /dev/null +++ b/api/delete_voter.php @@ -0,0 +1,27 @@ +prepare("DELETE FROM election_assignments WHERE user_id = ? AND election_id = ? AND role_in_election = 'Voter'"); + $stmt->execute([$userId, $electionId]); + + // Optional: Log the action + $currentUser = get_user(); + audit_log('voter_removed', 'users', $userId, null, null, "Removed voter ID $userId from election $electionId"); + + header("Location: ../voter_management.php?success=voter_deleted"); + exit; + } catch (PDOException $e) { + die("Error deleting voter: " . $e->getMessage()); + } +} else { + header("Location: ../voter_management.php"); + exit; +} diff --git a/api/import_voters.php b/api/import_voters.php new file mode 100644 index 0000000..2931105 --- /dev/null +++ b/api/import_voters.php @@ -0,0 +1,102 @@ +beginTransaction(); + + $handle = fopen($file["tmp_name"], "r"); + if ($handle === false) { + throw new Exception("Could not open the uploaded file."); + } + + // Skip header if it exists + $header = fgetcsv($handle); + // Basic header validation (optional, but good) + // Expected: student_id, name, email, track, grade_level + + $imported = 0; + $updated = 0; + + while (($data = fgetcsv($handle)) !== false) { + if (count($data) < 5) continue; // Skip malformed rows + + $student_id = trim($data[0]); + $name = trim($data[1]); + $email = trim($data[2]); + $track = trim($data[3]); + $grade_level = trim($data[4]); + + if (!$student_id || !$name || !$email) continue; + + // 1. Check if user already exists + $stmt = $pdo->prepare("SELECT id FROM users WHERE student_id = ? OR email = ?"); + $stmt->execute([$student_id, $email]); + $existing = $stmt->fetch(); + + if ($existing) { + $user_id = $existing["id"]; + // Update track/grade if needed + $upd = $pdo->prepare("UPDATE users SET track = ?, grade_level = ? WHERE id = ?"); + $upd->execute([$track, $grade_level, $user_id]); + $updated++; + } else { + // 1a. Create user in Supabase + $supabaseUser = SupabaseAuth::createUser($email, "iloilohns"); + $supabase_uid = null; + if ($supabaseUser['error']) { + if (str_contains(strtolower($supabaseUser['error']), 'already registered')) { + $sbUser = SupabaseAuth::getUserByEmail($email); + $supabase_uid = $sbUser['id'] ?? null; + } else { + // Log error but continue with other users? Or fail? + // Let's fail for now to be safe. + throw new Exception("Supabase Error for $email: " . $supabaseUser['error']); + } + } else { + $supabase_uid = $supabaseUser['data']['id'] ?? null; + } + + // Create new user locally + $user_id = uuid(); + $stmt = $pdo->prepare("INSERT INTO users (id, supabase_uid, student_id, name, email, track, grade_level, role) VALUES (?, ?, ?, ?, ?, ?, ?, 'Voter')"); + $stmt->execute([$user_id, $supabase_uid, $student_id, $name, $email, $track, $grade_level]); + $imported++; + } + + // 2. Assign to election + $chk = $pdo->prepare("SELECT COUNT(*) FROM election_assignments WHERE election_id = ? AND user_id = ?"); + $chk->execute([$election_id, $user_id]); + if ($chk->fetchColumn() == 0) { + $ea = $pdo->prepare("INSERT INTO election_assignments (id, election_id, user_id, role_in_election, assigned_by) VALUES (?, ?, ?, 'Voter', ?)"); + $ea->execute([uuid(), $election_id, $user_id, $_SESSION['user_id']]); + } + } + + fclose($handle); + audit_log("Imported voters via CSV", "users", "multiple"); + + $pdo->commit(); + header("Location: ../voter_management.php?success=import_complete&imported=$imported&updated=$updated"); + exit; + } catch (Exception $e) { + if (isset($pdo) && $pdo->inTransaction()) $pdo->rollBack(); + die("Error: " . $e->getMessage()); + } +} diff --git a/api/submit_vote.php b/api/submit_vote.php index a4f18ad..6c28f11 100644 --- a/api/submit_vote.php +++ b/api/submit_vote.php @@ -1,14 +1,14 @@ candidate_id +if ($_SERVER["REQUEST_METHOD"] === "POST") { + $election_id = $_POST["election_id"] ?? ""; + $raw_votes = $_POST["votes"] ?? []; // Array of position_id => candidate_id (string or array) $user = get_user(); - if (!$election_id || empty($votes)) { + if (!$election_id) { die("Invalid submission."); } @@ -19,40 +19,67 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { // 1. Verify election is ongoing $eStmt = $pdo->prepare("SELECT status FROM elections WHERE id = ?"); $eStmt->execute([$election_id]); - if ($eStmt->fetchColumn() !== 'Ongoing') { - throw new Exception("Election is not ongoing."); + $election = $eStmt->fetch(); + if (!$election || $election["status"] !== "Ongoing") { + throw new Exception("Election is not currently ongoing."); } // 2. Verify user hasn't voted yet $vCheck = $pdo->prepare("SELECT COUNT(*) FROM votes WHERE election_id = ? AND voter_id = ?"); - $vCheck->execute([$election_id, $user['id']]); + $vCheck->execute([$election_id, $user["id"]]); if ($vCheck->fetchColumn() > 0) { throw new Exception("You have already cast your vote for this election."); } - // 3. Insert votes + // 3. Prepare statement for inserting votes $stmt = $pdo->prepare("INSERT INTO votes (id, election_id, position_id, candidate_id, voter_id, ip_address, user_agent) VALUES (?, ?, ?, ?, ?, ?, ?)"); - foreach ($votes as $position_id => $candidate_id) { - $vote_id = uuid(); + // 4. Validate each position's votes + foreach ($raw_votes as $position_id => $candidate_id) { + if (empty($candidate_id)) continue; + + // Fetch position details + $pStmt = $pdo->prepare("SELECT * FROM positions WHERE id = ? AND election_id = ?"); + $pStmt->execute([$position_id, $election_id]); + $pos = $pStmt->fetch(); + + if (!$pos) { + throw new Exception("Invalid position detected."); + } + + // Validate candidate + $cStmt = $pdo->prepare("SELECT c.*, u.track FROM candidates c JOIN users u ON c.user_id = u.id WHERE c.id = ? AND c.position_id = ? AND c.approved = TRUE"); + $cStmt->execute([$candidate_id, $position_id]); + $cand = $cStmt->fetch(); + + if (!$cand) { + throw new Exception("Invalid or unapproved candidate selected."); + } + + // Check Track Specific logic + if ($pos["type"] === "Track Specific" && $cand["track"] !== $user["track"]) { + throw new Exception("Candidate track mismatch for position: " . $pos["name"]); + } + + // Insert vote $stmt->execute([ - $vote_id, + uuid(), $election_id, $position_id, $candidate_id, - $user['id'], - $_SERVER['REMOTE_ADDR'] ?? 'unknown', - $_SERVER['HTTP_USER_AGENT'] ?? 'unknown' + $user["id"], + $_SERVER["REMOTE_ADDR"] ?? "unknown", + $_SERVER["HTTP_USER_AGENT"] ?? "unknown" ]); } - audit_log('Cast ballot', 'elections', $election_id); + audit_log("Cast ballot", "elections", $election_id); $pdo->commit(); - header("Location: ../view_results.php?id=$election_id&success=vote_cast"); + header("Location: ../index.php?success=voted"); exit; } catch (Exception $e) { - if ($pdo->inTransaction()) $pdo->rollBack(); + if (isset($pdo) && $pdo->inTransaction()) $pdo->rollBack(); die("Error casting vote: " . $e->getMessage()); } } diff --git a/api/update_candidate.php b/api/update_candidate.php new file mode 100644 index 0000000..265d360 --- /dev/null +++ b/api/update_candidate.php @@ -0,0 +1,27 @@ +prepare("UPDATE candidates SET position_id = ?, party_name = ?, manifesto = ? WHERE id = ?"); + $stmt->execute([$positionId, $partyName, $manifesto, $candId]); + + $currentUser = get_user(); + audit_log('candidate_updated', 'candidates', $candId, null, null, "Updated candidate ID $candId"); + + header("Location: ../candidate_management.php?success=candidate_updated"); + exit; + } catch (PDOException $e) { + die("Error updating candidate: " . $e->getMessage()); + } +} else { + header("Location: ../candidate_management.php"); + exit; +} diff --git a/api/update_election.php b/api/update_election.php new file mode 100644 index 0000000..9c2b5ca --- /dev/null +++ b/api/update_election.php @@ -0,0 +1,29 @@ +prepare("UPDATE elections SET title = ?, description = ?, start_date_and_time = ?, end_date_and_time = ? WHERE id = ?"); + $stmt->execute([$title, $description, $startDate, $endDate, $id]); + + $currentUser = get_user(); + audit_log('election_updated', 'elections', $id, null, null, "Updated election $id"); + + header("Location: ../view_election.php?id=$id&success=1"); + exit; + } catch (PDOException $e) { + die("Error updating election: " . $e->getMessage()); + } +} else { + header("Location: ../index.php"); + exit; +} diff --git a/api/update_officer.php b/api/update_officer.php new file mode 100644 index 0000000..1e23417 --- /dev/null +++ b/api/update_officer.php @@ -0,0 +1,42 @@ +prepare("SELECT email, supabase_uid FROM users WHERE id = ?"); + $stmt->execute([$userId]); + $userRecord = $stmt->fetch(); + + if (!empty($password)) { + // Update Supabase password + if ($userRecord && $userRecord['supabase_uid']) { + SupabaseAuth::updateUserPassword($userRecord['supabase_uid'], $password); + } + + $stmt = $pdo->prepare("UPDATE users SET name = ?, email = ?, role = ? WHERE id = ?"); + $stmt->execute([$name, $email, $role, $userId]); + } else { + $stmt = $pdo->prepare("UPDATE users SET name = ?, email = ?, role = ? WHERE id = ?"); + $stmt->execute([$name, $email, $role, $userId]); + } + + $currentUser = get_user(); + audit_log('officer_updated', 'users', $userId, null, null, "Updated officer ID $userId info"); + + header("Location: ../officers_management.php?success=officer_updated"); + exit; + } catch (PDOException $e) { + die("Error updating officer: " . $e->getMessage()); + } +} else { + header("Location: ../officers_management.php"); + exit; +} diff --git a/api/update_voter.php b/api/update_voter.php new file mode 100644 index 0000000..53b5a54 --- /dev/null +++ b/api/update_voter.php @@ -0,0 +1,45 @@ +prepare("SELECT email, supabase_uid FROM users WHERE id = ?"); + $stmt->execute([$userId]); + $userRecord = $stmt->fetch(); + + if (!empty($password)) { + // Update Supabase password + if ($userRecord && $userRecord['supabase_uid']) { + SupabaseAuth::updateUserPassword($userRecord['supabase_uid'], $password); + } + + $stmt = $pdo->prepare("UPDATE users SET name = ?, student_id = ?, email = ?, track = ?, grade_level = ? WHERE id = ?"); + $stmt->execute([$name, $studentId, $email, $track, $gradeLevel, $userId]); + } else { + $stmt = $pdo->prepare("UPDATE users SET name = ?, student_id = ?, email = ?, track = ?, grade_level = ? WHERE id = ?"); + $stmt->execute([$name, $studentId, $email, $track, $gradeLevel, $userId]); + } + + // Log the action + $currentUser = get_user(); + audit_log('voter_updated', 'users', $userId, null, null, "Updated voter ID $userId info"); + + header("Location: ../voter_management.php?success=voter_updated"); + exit; + } catch (PDOException $e) { + die("Error updating voter: " . $e->getMessage()); + } +} else { + header("Location: ../voter_management.php"); + exit; +} diff --git a/auth_helper.php b/auth_helper.php index 3272267..c9cb5ee 100644 --- a/auth_helper.php +++ b/auth_helper.php @@ -1,6 +1,7 @@ prepare("INSERT INTO audit_logs (id, user_id, action, table_name, record_id, old_values, new_values, election_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); + $stmt = db()->prepare("INSERT INTO audit_logs (id, user_id, action, details, table_name, record_id, old_values, new_values, election_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"); $stmt->execute([ uuid(), $_SESSION['user_id'] ?? null, $action, + $details, $table, $record_id, $old ? json_encode($old) : null, diff --git a/ballot.php b/ballot.php index 297637f..d29b0dd 100644 --- a/ballot.php +++ b/ballot.php @@ -37,74 +37,150 @@ $positions = $positions->fetchAll();
Please select your candidates carefully. Your vote is immutable once cast.
+Your vote is secure and anonymous. Choose your representatives below.