I'm in a rush to release so I am adding features that are needed to make it usable.
This commit is contained in:
parent
c76ab1abf3
commit
4c2857b445
25 changed files with 2475 additions and 3475 deletions
|
@ -139,7 +139,19 @@ class DJ
|
|||
|
||||
private function loadDJMixes(): void
|
||||
{
|
||||
$stmt = $this->db->prepare("SELECT * FROM mix WHERE dj1 = ? OR dj2 = ? OR dj3 = ?");
|
||||
// Determine if the current user is an admin.
|
||||
$isAdmin = false;
|
||||
if (isset($_SESSION['user']) && isset($_SESSION['user']['role']) && $_SESSION['user']['role'] === 'admin') {
|
||||
$isAdmin = true;
|
||||
}
|
||||
|
||||
if ($isAdmin) {
|
||||
$stmt = $this->db->prepare("SELECT * FROM mix WHERE dj1 = ? OR dj2 = ? OR dj3 = ?");
|
||||
} else {
|
||||
// Only return mixes that are approved (pending = 0) for non-admin users.
|
||||
$stmt = $this->db->prepare("SELECT * FROM mix WHERE (dj1 = ? OR dj2 = ? OR dj3 = ?) AND pending = 0");
|
||||
}
|
||||
|
||||
$stmt->bind_param("iii", $this->id, $this->id, $this->id);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
|
@ -149,10 +161,9 @@ class DJ
|
|||
}
|
||||
$stmt->close();
|
||||
$this->mixes = $mixes;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private function loadBySlug(): bool
|
||||
{
|
||||
$socials = [];
|
||||
|
|
|
@ -57,7 +57,15 @@ class Mix
|
|||
|
||||
private function get_mix_by_id()
|
||||
{
|
||||
$stmt = $this->db->prepare("SELECT * FROM mix WHERE id = ?");
|
||||
// Check if current user is admin
|
||||
$isAdmin = (isset($_SESSION['user']) && isset($_SESSION['user']['role']) && $_SESSION['user']['role'] === 'admin');
|
||||
|
||||
if ($isAdmin) {
|
||||
$stmt = $this->db->prepare("SELECT * FROM mix WHERE id = ?");
|
||||
} else {
|
||||
// Only return approved mixes (pending = 0) for non-admins
|
||||
$stmt = $this->db->prepare("SELECT * FROM mix WHERE id = ? AND pending = 0");
|
||||
}
|
||||
$stmt->bind_param("i", $this->id);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
|
@ -65,7 +73,6 @@ class Mix
|
|||
$stmt->close();
|
||||
return $mix;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $mix
|
||||
* @return true
|
||||
|
@ -213,7 +220,14 @@ class Mix
|
|||
|
||||
private function get_mix_by_slug()
|
||||
{
|
||||
$stmt = $this->db->prepare("SELECT * FROM mix WHERE slug = ?");
|
||||
// Check if current user is admin
|
||||
$isAdmin = (isset($_SESSION['user']) && isset($_SESSION['user']['role']) && $_SESSION['user']['role'] === 'admin');
|
||||
|
||||
if ($isAdmin) {
|
||||
$stmt = $this->db->prepare("SELECT * FROM mix WHERE slug = ?");
|
||||
} else {
|
||||
$stmt = $this->db->prepare("SELECT * FROM mix WHERE slug = ? AND pending = 0");
|
||||
}
|
||||
$stmt->bind_param("s", $this->slug);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
|
|
110
classes/User.php
110
classes/User.php
|
@ -61,8 +61,116 @@ Class User{
|
|||
return $user;
|
||||
}
|
||||
|
||||
public function login(mixed $username, mixed $password)
|
||||
public function login($email, $password)
|
||||
{
|
||||
// Retrieve user record by email
|
||||
$stmt = $this->db->prepare("SELECT * FROM users WHERE email = ?");
|
||||
$stmt->bind_param("s", $email);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
$user_data = $result->fetch_assoc();
|
||||
$stmt->close();
|
||||
|
||||
// Check login_attempts table for lockout status
|
||||
$stmt = $this->db->prepare("SELECT * FROM login_attempts WHERE email = ?");
|
||||
$stmt->bind_param("s", $email);
|
||||
$stmt->execute();
|
||||
$attempt_data = $stmt->get_result()->fetch_assoc();
|
||||
$stmt->close();
|
||||
|
||||
$current_time = new \DateTime();
|
||||
|
||||
if ($attempt_data && !empty($attempt_data['lockout_until'])) {
|
||||
$lockout_until = new \DateTime($attempt_data['lockout_until']);
|
||||
if ($current_time < $lockout_until) {
|
||||
return "Account locked until " . $lockout_until->format('Y-m-d H:i:s') . ". Please try again later.";
|
||||
}
|
||||
}
|
||||
|
||||
// If no user record found, still update login_attempts to mitigate enumeration issues
|
||||
if (!$user_data) {
|
||||
$this->updateFailedAttempt($email);
|
||||
return "Invalid email or password.";
|
||||
}
|
||||
|
||||
// Verify the password using password_verify
|
||||
if (password_verify($password, $user_data['password'])) {
|
||||
// Successful login – clear login attempts and set session variables
|
||||
$this->resetLoginAttempts($email);
|
||||
$_SESSION['user'] = [
|
||||
'id' => $user_data['id'],
|
||||
'email' => $user_data['email'],
|
||||
'username' => $user_data['username'],
|
||||
'role' => $user_data['isAdmin'] ? 'admin' : 'user'
|
||||
];
|
||||
return true;
|
||||
} else {
|
||||
$attempts = $this->updateFailedAttempt($email);
|
||||
return "Invalid email or password. Attempt $attempts of 3.";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update (or create) a record in the login_attempts table for a failed attempt.
|
||||
* If attempts reach 3, set a lockout that doubles each time.
|
||||
* Returns the current number of attempts.
|
||||
*/
|
||||
private function updateFailedAttempt($email)
|
||||
{
|
||||
// Check for an existing record
|
||||
$stmt = $this->db->prepare("SELECT * FROM login_attempts WHERE email = ?");
|
||||
$stmt->bind_param("s", $email);
|
||||
$stmt->execute();
|
||||
$record = $stmt->get_result()->fetch_assoc();
|
||||
$stmt->close();
|
||||
|
||||
$current_time = new \DateTime();
|
||||
|
||||
if ($record) {
|
||||
$attempts = $record['attempts'] + 1;
|
||||
$lockouts = $record['lockouts'];
|
||||
if ($attempts >= 3) {
|
||||
// Increment lockouts and calculate the new lockout duration:
|
||||
// Duration in minutes = 30 * 2^(lockouts)
|
||||
$lockouts++;
|
||||
$duration = 30 * pow(2, $lockouts - 1);
|
||||
$lockout_until = clone $current_time;
|
||||
$lockout_until->modify("+{$duration} minutes");
|
||||
// Reset attempts to 0 on lockout
|
||||
$attempts = 0;
|
||||
$stmt = $this->db->prepare("UPDATE login_attempts SET attempts = ?, lockouts = ?, last_attempt = NOW(), lockout_until = ? WHERE email = ?");
|
||||
$lockout_until_str = $lockout_until->format('Y-m-d H:i:s');
|
||||
$stmt->bind_param("iiss", $attempts, $lockouts, $lockout_until_str, $email);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
} else {
|
||||
$stmt = $this->db->prepare("UPDATE login_attempts SET attempts = ?, last_attempt = NOW() WHERE email = ?");
|
||||
$stmt->bind_param("is", $attempts, $email);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
}
|
||||
return $attempts;
|
||||
} else {
|
||||
// Create a new record for this email
|
||||
$attempts = 1;
|
||||
$lockouts = 0;
|
||||
$stmt = $this->db->prepare("INSERT INTO login_attempts (email, attempts, lockouts, last_attempt) VALUES (?, ?, ?, NOW())");
|
||||
$stmt->bind_param("sii", $email, $attempts, $lockouts);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
return $attempts;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the login_attempts record for the given email.
|
||||
*/
|
||||
private function resetLoginAttempts($email)
|
||||
{
|
||||
$stmt = $this->db->prepare("DELETE FROM login_attempts WHERE email = ?");
|
||||
$stmt->bind_param("s", $email);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -15,7 +15,8 @@
|
|||
"yosymfony/toml": "*",
|
||||
"ext-mysqli": "*",
|
||||
"ext-curl": "*",
|
||||
"phpunit/phpunit": ">8.5.1.0"
|
||||
"phpunit/phpunit": ">8.5.1.0",
|
||||
"aws/aws-sdk-php": "*"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
|
14
dj.php
14
dj.php
|
@ -233,15 +233,11 @@ require_once 'includes/header.php';
|
|||
$utm_params = '?utm_source=website&utm_medium=share_modal&utm_campaign=sharing';
|
||||
$share_url = urlencode($url . $utm_params);
|
||||
?>
|
||||
<a href="#" id="copyLinkBtn" class="btn btn-secondary w-100 mb-2">Copy URL</a>
|
||||
<a href="#" id="copyLinkBtn" class="btn btn-secondary w-100 mb-2"><?php echo $locale['copyurl'];?></a>
|
||||
<a href="https://www.facebook.com/sharer/sharer.php?u=<?php echo $share_url; ?>"
|
||||
target="_blank" class="btn btn-primary w-100 mb-2" onclick="hideModal()">Share to
|
||||
Facebook</a>
|
||||
target="_blank" class="btn btn-primary w-100 mb-2" onclick="hideModal()"><?php echo $locale['sharetofb'];?></a>
|
||||
<a href="https://twitter.com/intent/tweet?url=<?php echo $share_url; ?>"
|
||||
target="_blank" class="btn btn-info w-100 mb-2" onclick="hideModal()">Share to
|
||||
Twitter</a>
|
||||
<a href="https://www.instagram.com/" target="_blank" class="btn btn-danger w-100"
|
||||
onclick="hideModal()">Share to Instagram</a>
|
||||
target="_blank" class="btn btn-info w-100 mb-2" onclick="hideModal()"><?php echo $locale['sharetotwitter'];?></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -260,10 +256,10 @@ require_once 'includes/header.php';
|
|||
|
||||
copyLinkBtn.onclick = function () {
|
||||
navigator.clipboard.writeText(urlToCopy).then(function () {
|
||||
alert('URL copied to clipboard');
|
||||
alert('<?php echo $locale['urlcopiedtoclipboard'];?>');
|
||||
shareModal.hide();
|
||||
}, function (err) {
|
||||
alert('Failed to copy URL: ' + err);
|
||||
alert('<?php echo $locale['failedtocopyurl'];?>: ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
121
forgot-password.php
Normal file
121
forgot-password.php
Normal file
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
session_start();
|
||||
require_once 'includes/globals.php';
|
||||
require_once 'vendor/autoload.php';
|
||||
|
||||
use DJMixHosting\Database;
|
||||
use Aws\Ses\SesClient;
|
||||
use Aws\Exception\AwsException;
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['email'])) {
|
||||
$email = trim($_POST['email']);
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
$_SESSION['error'] = "Invalid email format.";
|
||||
header("Location: forgot-password.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
$db = new Database($config);
|
||||
// Check if email exists in the system
|
||||
$stmt = $db->prepare("SELECT id, username FROM users WHERE email = ?");
|
||||
$stmt->bind_param("s", $email);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
$userData = $result->fetch_assoc();
|
||||
$stmt->close();
|
||||
|
||||
// Always show a success message (to avoid disclosing which emails are registered)
|
||||
$_SESSION['success'] = "If the email exists in our system, a password reset link has been sent.";
|
||||
|
||||
if ($userData) {
|
||||
$user_id = $userData['id'];
|
||||
// Generate a password reset verification code valid for 15 minutes
|
||||
$verification_code = bin2hex(random_bytes(16));
|
||||
$expires_at = date("Y-m-d H:i:s", strtotime("+15 minutes"));
|
||||
|
||||
// Insert a record with purpose 'password_reset'
|
||||
$stmt = $db->prepare("REPLACE INTO email_verifications (user_id, email, verification_code, expires_at, purpose) VALUES (?, ?, ?, ?, 'password_reset')");
|
||||
$stmt->bind_param("isss", $user_id, $email, $verification_code, $expires_at);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
// Send password reset email via AWS SES
|
||||
$sesClient = new SesClient([
|
||||
'version' => 'latest',
|
||||
'region' => $config['aws']['ses']['region'],
|
||||
'credentials' => [
|
||||
'key' => $config['aws']['ses']['access_key'],
|
||||
'secret' => $config['aws']['ses']['secret_key'],
|
||||
]
|
||||
]);
|
||||
|
||||
$sender_email = $config['aws']['ses']['sender_email'];
|
||||
$recipient_email = $email;
|
||||
$subject = "Password Reset Request";
|
||||
$reset_link = $config['app']['url'] . "/password-reset.php?code={$verification_code}";
|
||||
$body_text = "You have requested to reset your password. Please click the link below to reset your password:\n\n";
|
||||
$body_text .= "{$reset_link}\n\nIf you did not request this, please ignore this email. This link will expire in 15 minutes.";
|
||||
|
||||
try {
|
||||
$result = $sesClient->sendEmail([
|
||||
'Destination' => [
|
||||
'ToAddresses' => [$recipient_email],
|
||||
],
|
||||
'ReplyToAddresses' => [$sender_email],
|
||||
'Source' => $sender_email,
|
||||
'Message' => [
|
||||
'Body' => [
|
||||
'Text' => [
|
||||
'Charset' => 'UTF-8',
|
||||
'Data' => $body_text,
|
||||
],
|
||||
],
|
||||
'Subject' => [
|
||||
'Charset' => 'UTF-8',
|
||||
'Data' => $subject,
|
||||
],
|
||||
],
|
||||
]);
|
||||
} catch (AwsException $e) {
|
||||
// Optionally log the error without disclosing details to the user.
|
||||
}
|
||||
}
|
||||
header("Location: forgot-password.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once 'includes/header.php';
|
||||
?>
|
||||
|
||||
<section class="forgot-password-section py-5">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-5">
|
||||
<?php
|
||||
if(isset($_SESSION['error'])) {
|
||||
echo '<div class="alert alert-danger alert-dismissible fade show mb-4" role="alert">' . htmlspecialchars($_SESSION['error']) . '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
|
||||
unset($_SESSION['error']);
|
||||
}
|
||||
if(isset($_SESSION['success'])) {
|
||||
echo '<div class="alert alert-success alert-dismissible fade show mb-4" role="alert">' . htmlspecialchars($_SESSION['success']) . '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
|
||||
unset($_SESSION['success']);
|
||||
}
|
||||
?>
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<h3 class="text-center mb-4">Forgot Password</h3>
|
||||
<form action="forgot-password.php" method="post" class="needs-validation" novalidate>
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">Enter your email address</label>
|
||||
<input type="email" class="form-control" id="email" name="email" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php require_once 'includes/footer.php'; ?>
|
14
genre.php
14
genre.php
|
@ -156,15 +156,11 @@ require_once 'includes/header.php';
|
|||
$utm_params = '?utm_source=website&utm_medium=share_modal&utm_campaign=sharing';
|
||||
$share_url = urlencode($url . $utm_params);
|
||||
?>
|
||||
<a href="#" id="copyLinkBtn" class="btn btn-secondary w-100 mb-2">Copy URL</a>
|
||||
<a href="#" id="copyLinkBtn" class="btn btn-secondary w-100 mb-2"><?php echo $locale['copyurl'];?></a>
|
||||
<a href="https://www.facebook.com/sharer/sharer.php?u=<?php echo $share_url; ?>"
|
||||
target="_blank" class="btn btn-primary w-100 mb-2" onclick="hideModal()">Share to
|
||||
Facebook</a>
|
||||
target="_blank" class="btn btn-primary w-100 mb-2" onclick="hideModal()"><?php echo $locale['sharetofb'];?></a>
|
||||
<a href="https://twitter.com/intent/tweet?url=<?php echo $share_url; ?>"
|
||||
target="_blank" class="btn btn-info w-100 mb-2" onclick="hideModal()">Share to
|
||||
Twitter</a>
|
||||
<a href="https://www.instagram.com/" target="_blank" class="btn btn-danger w-100"
|
||||
onclick="hideModal()">Share to Instagram</a>
|
||||
target="_blank" class="btn btn-dark w-100 mb-2" onclick="hideModal()"><?php echo $locale['sharetotwitter'];?></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -183,10 +179,10 @@ require_once 'includes/header.php';
|
|||
|
||||
copyLinkBtn.onclick = function () {
|
||||
navigator.clipboard.writeText(urlToCopy).then(function () {
|
||||
alert('URL copied to clipboard');
|
||||
alert('<?php echo $locale['urlcopiedtoclipboard'];?>');
|
||||
shareModal.hide();
|
||||
}, function (err) {
|
||||
alert('Failed to copy URL: ' + err);
|
||||
alert('<?php echo $locale['failedtocopyurl'];?>: ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
4
includes/.htaccess
Normal file
4
includes/.htaccess
Normal file
|
@ -0,0 +1,4 @@
|
|||
<Files "config.toml">
|
||||
Order allow,deny
|
||||
Deny from all
|
||||
</Files>
|
|
@ -11,3 +11,8 @@ if (isset($_GET['lang'])) {
|
|||
}
|
||||
|
||||
$locale = loadLocale($lang);
|
||||
|
||||
function langPrintString($string, $lang)
|
||||
{
|
||||
echo $lang[$string];
|
||||
}
|
|
@ -65,7 +65,12 @@ $current_lang = $_SESSION['lang'] ?? $config['app']['locale'];
|
|||
<select class="form-select" id="languageSelect"
|
||||
onchange="location = this.value;">
|
||||
<?php
|
||||
$currentUrl = strtok($_SERVER["REQUEST_URI"], '?');
|
||||
if (isset($_SERVER["REQUIEST_URI"])) {
|
||||
$currentUrl = strtok($_SERVER["REQUEST_URI"], '?') ?? '/';
|
||||
} else {
|
||||
$currentUrl = '/';
|
||||
}
|
||||
|
||||
$queryParams = $_GET;
|
||||
foreach ($languages as $key => $value) {
|
||||
$queryParams['lang'] = $key;
|
||||
|
|
|
@ -67,5 +67,8 @@ return [
|
|||
"sharetofb" => "Share to Facebook",
|
||||
"sharetoig" => "Share to Instagram",
|
||||
"copyurl" => "Copy URL",
|
||||
|
||||
"urlcopiedtoclipboard" => "URL copied to clipboard",
|
||||
"failedtocopyurl" => "Failed to copy URL",
|
||||
"name" => "Name",
|
||||
"username" => "Username",
|
||||
];
|
171
login.php
171
login.php
|
@ -1,66 +1,149 @@
|
|||
<?php
|
||||
require_once 'includes/globals.php';
|
||||
|
||||
if (isset($_SESSION['user'])) {
|
||||
header("Location: /profile");
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once 'vendor/autoload.php';
|
||||
|
||||
use DJMixHosting\Database;
|
||||
use DJMixHosting\User;
|
||||
|
||||
// Generate a CSRF token if one is not set
|
||||
if (!isset($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
|
||||
$title = $locale['home'];
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||
|
||||
if (isset($_POST['email'], $_POST['password'], $_POST['csrf_token'])) {
|
||||
|
||||
if (isset($_POST['username']) && isset($_POST['password'])) {
|
||||
$username = $_POST['username'];
|
||||
$password = $_POST['password'];
|
||||
// Check the CSRF token
|
||||
if ($_POST['csrf_token'] !== $_SESSION['csrf_token']) {
|
||||
$_SESSION['error'] = $locale['message'] . ": Invalid form submission. Please try again.";
|
||||
} else {
|
||||
$email = $_POST['email'];
|
||||
$password = $_POST['password'];
|
||||
|
||||
$db = new Database($config);
|
||||
$user = new User($db);
|
||||
$user->login($username, $password);
|
||||
$db = new Database($config);
|
||||
$user = new User($db);
|
||||
$result = $user->login($email, $password);
|
||||
if ($result === true) {
|
||||
// Successful login, redirect to profile page
|
||||
header("Location: profile.php");
|
||||
exit;
|
||||
} else {
|
||||
// Set error message from login method (includes lockout messages)
|
||||
$_SESSION['error'] = $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
require_once 'includes/header.php'; ?>
|
||||
<section>
|
||||
<div class="container py-5">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<nav aria-label="breadcrumb" class="bg-body-tertiary rounded-3 p-3 mb-4">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="/"><?php echo $locale['home']; ?></a></li>
|
||||
<li class="breadcrumb-item active"><a href="/login.php"><?php echo $locale['login']; ?></a>
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="container py-5">
|
||||
<div class="row">
|
||||
<div class="col-lg-6 offset-lg-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><?php echo $locale['login']; ?></h5>
|
||||
<form action="login.php" method="post">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label"><?php echo $locale['username']; ?></label>
|
||||
<input type="text" class="form-control" id="username" name="username" required>
|
||||
require_once 'includes/header.php';
|
||||
?>
|
||||
<section class="login-section py-5">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-5">
|
||||
<?php
|
||||
if (isset($_SESSION['error'])) {
|
||||
echo '<div class="alert alert-danger alert-dismissible fade show mb-4" role="alert">
|
||||
' . htmlspecialchars($_SESSION['error']) . '
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>';
|
||||
unset($_SESSION['error']);
|
||||
}
|
||||
?>
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<h3 class="text-center mb-4">Login</h3>
|
||||
<form action="login.php" method="post" class="needs-validation" novalidate>
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">Email</label>
|
||||
<input type="email" class="form-control form-control-lg" id="email" name="email" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<div class="input-group">
|
||||
<input type="password" class="form-control form-control-lg" id="password" name="password" required>
|
||||
<button class="btn btn-outline-secondary px-3" type="button" id="togglePassword"
|
||||
style="border-left: none;">
|
||||
<i class="bi bi-eye-fill fs-5"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label"><?php echo $locale['password']; ?></label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary"><?php echo $locale['login']; ?></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-4 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="rememberMe" name="remember_me">
|
||||
<label class="form-check-label" for="rememberMe">Remember me</label>
|
||||
</div>
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
|
||||
<button type="submit" class="btn btn-primary w-100 btn-lg mb-3">Login</button>
|
||||
<div class="text-center">
|
||||
<a href="forgot-password.php" class="text-decoration-none">Forgot password?</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center mt-4">
|
||||
<p class="mb-0">Don't have an account? <a href="register.php" class="text-decoration-none">Sign up</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</section>
|
||||
<style>
|
||||
.login-section {
|
||||
min-height: calc(100vh - 200px); /* Adjust based on your header/footer height */
|
||||
}
|
||||
|
||||
<?php require_once 'includes/footer.php'; ?>
|
||||
.form-control:focus {
|
||||
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.15);
|
||||
}
|
||||
|
||||
#togglePassword:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
#togglePassword:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.input-group .form-control:focus {
|
||||
border-right: 1px solid #86b7fe;
|
||||
}
|
||||
|
||||
.input-group .btn {
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Form validation
|
||||
const form = document.querySelector('.needs-validation');
|
||||
form.addEventListener('submit', function(event) {
|
||||
if (!form.checkValidity()) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
form.classList.add('was-validated');
|
||||
}, false);
|
||||
|
||||
// Password visibility toggle with improved UX
|
||||
const togglePassword = document.querySelector('#togglePassword');
|
||||
const password = document.querySelector('#password');
|
||||
togglePassword.addEventListener('click', function() {
|
||||
const type = password.getAttribute('type') === 'password' ? 'text' : 'password';
|
||||
password.setAttribute('type', type);
|
||||
this.querySelector('i').classList.toggle('bi-eye-fill');
|
||||
this.querySelector('i').classList.toggle('bi-eye-slash-fill');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?php require_once 'includes/footer.php'; ?>
|
||||
|
|
62
mix.php
62
mix.php
|
@ -39,7 +39,7 @@ require_once 'includes/header.php'; ?>
|
|||
<nav aria-label="breadcrumb" class="bg-body-tertiary rounded-3 p-3 mb-4">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="/"><?php echo $locale['home']; ?></a></li>
|
||||
<li class="breadcrumb-item"><a href="/mix"><?php echo $locale['mixes']; ?></a></li>
|
||||
<li class="breadcrumb-item"><?php echo $locale['mixes']; ?></li>
|
||||
<li class="breadcrumb-item active"
|
||||
aria-current="page"><?php
|
||||
if (isset($mix) && $mix->get_name() != "") {
|
||||
|
@ -76,27 +76,22 @@ require_once 'includes/header.php'; ?>
|
|||
<div class="card-body bg-body-secondary text-center">
|
||||
<?php
|
||||
if ($mix->is_download_only()) {
|
||||
echo "<a href='/mix/" . $mix->get_slug() . "/download" . "' class='btn btn-primary'>" . $locale['download'] . "</a>";
|
||||
echo "<a href='/mix/" . $mix->get_slug() . "/download" . "' class='btn btn-primary w-100 mb-2'>" . $locale['download'] . "</a>";
|
||||
} else {
|
||||
?>
|
||||
<div id="audio-player">
|
||||
<audio id="audio" src="<?php echo $mix->get_url(); ?>"></audio>
|
||||
<div class="player-controls">
|
||||
<button id="play-pause-btn">
|
||||
<i class="fas fa-play" style="font-size: 12px;"></i>
|
||||
</button>
|
||||
<input type="range" id="seek-bar" value="0">
|
||||
?>
|
||||
<div id="audio-player">
|
||||
<audio id="audio" src="<?php echo $mix->get_url(); ?>"></audio>
|
||||
<div class="player-controls">
|
||||
<button id="play-pause-btn">
|
||||
<i class="fas fa-play" style="font-size: 12px;"></i>
|
||||
</button>
|
||||
<input type="range" id="seek-bar" value="0">
|
||||
</div>
|
||||
<div id="time-display">
|
||||
<span id="current-time">0:00</span> / <span id="duration">0:00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="time-display">
|
||||
<span id="current-time">0:00</span> / <span id="duration">0:00</span>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-4">
|
||||
|
@ -398,14 +393,14 @@ require_once 'includes/header.php'; ?>
|
|||
$utm_params = '?utm_source=website&utm_medium=share_modal&utm_campaign=sharing';
|
||||
$share_url = urlencode($url . $utm_params);
|
||||
?>
|
||||
<a href="#" id="copyLinkBtn" class="btn btn-secondary w-100 mb-2">Copy URL</a>
|
||||
<a href="#" id="copyLinkBtn"
|
||||
class="btn btn-secondary w-100 mb-2"><?php echo $locale['copyurl']; ?></a>
|
||||
<a href="https://www.facebook.com/sharer/sharer.php?u=<?php echo $share_url; ?>"
|
||||
target="_blank" class="btn btn-primary w-100 mb-2" onclick="hideModal()">Share to
|
||||
Facebook</a>
|
||||
target="_blank" class="btn btn-primary w-100 mb-2"
|
||||
onclick="hideModal()"><?php echo $locale['sharetofb']; ?></a>
|
||||
<a href="https://twitter.com/intent/tweet?url=<?php echo $share_url; ?>"
|
||||
target="_blank" class="btn btn-dark w-100 mb-2" onclick="hideModal()">Share to X (formerly Twitter)</a>
|
||||
<a href="https://www.instagram.com/" target="_blank" class="btn btn-danger w-100"
|
||||
onclick="hideModal()">Share to Instagram</a>
|
||||
target="_blank" class="btn btn-dark w-100 mb-2"
|
||||
onclick="hideModal()"><?php echo $locale['sharetotwitter']; ?></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -426,10 +421,10 @@ require_once 'includes/header.php'; ?>
|
|||
|
||||
copyLinkBtn.onclick = function () {
|
||||
navigator.clipboard.writeText(urlToCopy).then(function () {
|
||||
alert('URL copied to clipboard');
|
||||
alert('<?php echo $locale['urlcopiedtoclipboard'];?>');
|
||||
shareModal.hide();
|
||||
}, function (err) {
|
||||
alert('Failed to copy URL: ' + err);
|
||||
alert('<?php echo $locale['failedtocopyurl'];?>: ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -518,7 +513,7 @@ require_once 'includes/header.php'; ?>
|
|||
|
||||
</script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$(document).ready(function () {
|
||||
const audio = $('#audio')[0];
|
||||
const playPauseBtn = $('#play-pause-btn');
|
||||
const playPauseIcon = playPauseBtn.find('i');
|
||||
|
@ -548,31 +543,30 @@ require_once 'includes/header.php'; ?>
|
|||
}
|
||||
}
|
||||
|
||||
audio.addEventListener('loadedmetadata', function() {
|
||||
audio.addEventListener('loadedmetadata', function () {
|
||||
seekBar.attr('max', audio.duration);
|
||||
duration.text(formatTime(audio.duration));
|
||||
});
|
||||
|
||||
playPauseBtn.click(togglePlayPause);
|
||||
|
||||
seekBar.on('input', function() {
|
||||
seekBar.on('input', function () {
|
||||
const time = seekBar.val();
|
||||
audio.currentTime = time;
|
||||
});
|
||||
|
||||
audio.addEventListener('timeupdate', function() {
|
||||
audio.addEventListener('timeupdate', function () {
|
||||
seekBar.val(audio.currentTime);
|
||||
currentTime.text(formatTime(audio.currentTime));
|
||||
});
|
||||
|
||||
audio.addEventListener('ended', function() {
|
||||
audio.addEventListener('ended', function () {
|
||||
playPauseIcon.removeClass('fa-pause').addClass('fa-play');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<?php else: ?>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
|
|
13
mixshow.php
13
mixshow.php
|
@ -177,14 +177,11 @@ require_once 'includes/header.php';
|
|||
$utm_params = '?utm_source=website&utm_medium=share_modal&utm_campaign=sharing';
|
||||
$share_url = urlencode($url . $utm_params);
|
||||
?>
|
||||
<a href="#" id="copyLinkBtn" class="btn btn-secondary w-100 mb-2">Copy URL</a>
|
||||
<a href="#" id="copyLinkBtn" class="btn btn-secondary w-100 mb-2"><?php echo $locale['copyurl'];?></a>
|
||||
<a href="https://www.facebook.com/sharer/sharer.php?u=<?php echo $share_url; ?>"
|
||||
target="_blank" class="btn btn-primary w-100 mb-2" onclick="hideModal()">Share to
|
||||
Facebook</a>
|
||||
target="_blank" class="btn btn-primary w-100 mb-2" onclick="hideModal()"><?php echo $locale['sharetofb'];?></a>
|
||||
<a href="https://twitter.com/intent/tweet?url=<?php echo $share_url; ?>"
|
||||
target="_blank" class="btn btn-info w-100 mb-2" onclick="hideModal()">Share to Twitter</a>
|
||||
<a href="https://www.instagram.com/" target="_blank" class="btn btn-danger w-100"
|
||||
onclick="hideModal()">Share to Instagram</a>
|
||||
target="_blank" class="btn btn-dark w-100 mb-2" onclick="hideModal()"><?php echo $locale['sharetotwitter'];?></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -205,10 +202,10 @@ require_once 'includes/header.php';
|
|||
|
||||
copyLinkBtn.onclick = function () {
|
||||
navigator.clipboard.writeText(urlToCopy).then(function () {
|
||||
alert('URL copied to clipboard');
|
||||
alert('<?php echo $locale['urlcopiedtoclipboard'];?>');
|
||||
shareModal.hide();
|
||||
}, function (err) {
|
||||
alert('Failed to copy URL: ' + err);
|
||||
alert('<?php echo $locale['failedtocopyurl'];?>: ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
124
password-reset.php
Normal file
124
password-reset.php
Normal file
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
session_start();
|
||||
require_once 'includes/globals.php';
|
||||
require_once 'vendor/autoload.php';
|
||||
|
||||
use DJMixHosting\Database;
|
||||
|
||||
$db = new Database($config);
|
||||
$verification_code = "";
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['code'])) {
|
||||
$verification_code = trim($_GET['code']);
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$verification_code = trim($_POST['verification_code'] ?? '');
|
||||
$username = trim($_POST['username'] ?? '');
|
||||
$new_password = $_POST['new_password'] ?? '';
|
||||
$confirm_password = $_POST['confirm_password'] ?? '';
|
||||
|
||||
if (empty($verification_code) || empty($username) || empty($new_password) || empty($confirm_password)) {
|
||||
$_SESSION['error'] = "All fields are required.";
|
||||
header("Location: password-reset.php?code=" . urlencode($verification_code));
|
||||
exit;
|
||||
}
|
||||
if ($new_password !== $confirm_password) {
|
||||
$_SESSION['error'] = "Passwords do not match.";
|
||||
header("Location: password-reset.php?code=" . urlencode($verification_code));
|
||||
exit;
|
||||
}
|
||||
|
||||
// Look up the password reset record (purpose 'password_reset')
|
||||
$stmt = $db->prepare("SELECT * FROM email_verifications WHERE verification_code = ? AND purpose = 'password_reset'");
|
||||
$stmt->bind_param("s", $verification_code);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
$record = $result->fetch_assoc();
|
||||
$stmt->close();
|
||||
|
||||
if (!$record) {
|
||||
$_SESSION['error'] = "Invalid or expired password reset code.";
|
||||
header("Location: password-reset.php?code=" . urlencode($verification_code));
|
||||
exit;
|
||||
}
|
||||
|
||||
// Check expiration
|
||||
$current_time = new DateTime();
|
||||
$expires_at = new DateTime($record['expires_at']);
|
||||
if ($current_time > $expires_at) {
|
||||
$_SESSION['error'] = "Password reset code has expired.";
|
||||
header("Location: password-reset.php?code=" . urlencode($verification_code));
|
||||
exit;
|
||||
}
|
||||
|
||||
// Verify the username matches the record
|
||||
$stmt = $db->prepare("SELECT id, username FROM users WHERE id = ? AND username = ?");
|
||||
$stmt->bind_param("is", $record['user_id'], $username);
|
||||
$stmt->execute();
|
||||
$userData = $stmt->get_result()->fetch_assoc();
|
||||
$stmt->close();
|
||||
|
||||
if (!$userData) {
|
||||
$_SESSION['error'] = "Username does not match our records.";
|
||||
header("Location: password-reset.php?code=" . urlencode($verification_code));
|
||||
exit;
|
||||
}
|
||||
|
||||
// Update the user's password
|
||||
$hashed_password = password_hash($new_password, PASSWORD_DEFAULT);
|
||||
$stmt = $db->prepare("UPDATE users SET password = ? WHERE id = ?");
|
||||
$stmt->bind_param("si", $hashed_password, $userData['id']);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
// Remove the password reset record
|
||||
$stmt = $db->prepare("DELETE FROM email_verifications WHERE verification_code = ? AND purpose = 'password_reset'");
|
||||
$stmt->bind_param("s", $verification_code);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
$_SESSION['success'] = "Your password has been reset successfully. Please log in with your new password.";
|
||||
header("Location: login.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once 'includes/header.php';
|
||||
?>
|
||||
|
||||
<section class="password-reset-section py-5">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-5">
|
||||
<?php
|
||||
if(isset($_SESSION['error'])) {
|
||||
echo '<div class="alert alert-danger alert-dismissible fade show mb-4" role="alert">' . htmlspecialchars($_SESSION['error']) . '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
|
||||
unset($_SESSION['error']);
|
||||
}
|
||||
?>
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<h3 class="text-center mb-4">Reset Password</h3>
|
||||
<form action="password-reset.php" method="post" class="needs-validation" novalidate>
|
||||
<input type="hidden" name="verification_code" value="<?php echo htmlspecialchars($verification_code); ?>">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Enter your username</label>
|
||||
<input type="text" class="form-control" id="username" name="username" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="new_password" class="form-label">New Password</label>
|
||||
<input type="password" class="form-control" id="new_password" name="new_password" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="confirm_password" class="form-label">Confirm New Password</label>
|
||||
<input type="password" class="form-control" id="confirm_password" name="confirm_password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100">Reset Password</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php require_once 'includes/footer.php'; ?>
|
4059
privacy.php
4059
privacy.php
File diff suppressed because one or more lines are too long
278
profile.php
Normal file
278
profile.php
Normal file
|
@ -0,0 +1,278 @@
|
|||
<?php
|
||||
// profile.php
|
||||
|
||||
require_once 'includes/globals.php';
|
||||
require_once 'vendor/autoload.php';
|
||||
|
||||
// Make sure the user is authenticated; otherwise redirect to login.
|
||||
if (!isset($_SESSION['user'])) {
|
||||
header("Location: login.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
use DJMixHosting\Database;
|
||||
$db = new Database($config);
|
||||
|
||||
// Retrieve the full user record from the database
|
||||
$userId = $_SESSION['user']['id'];
|
||||
$stmt = $db->prepare("SELECT * FROM users WHERE id = ?");
|
||||
$stmt->bind_param("i", $userId);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
$userData = $result->fetch_assoc();
|
||||
$stmt->close();
|
||||
|
||||
// Determine if editing should be disabled (if email not verified)
|
||||
$editingDisabled = (int)$userData['emailVerified'] !== 1; // Assuming 1 means verified
|
||||
|
||||
// Optionally, set a flag for showing an alert to verify email.
|
||||
$alertMessage = "";
|
||||
if ($editingDisabled) {
|
||||
$alertMessage = "Please verify your email to enable profile editing.";
|
||||
}
|
||||
|
||||
require_once 'includes/header.php';
|
||||
?>
|
||||
|
||||
<section class="container py-5">
|
||||
<?php if (!empty($alertMessage)): ?>
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<?php echo $alertMessage; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="row">
|
||||
<!-- Left Sidebar: Profile Picture and Controls -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card mb-4">
|
||||
<div class="card-body bg-body-secondary text-center">
|
||||
<img src="<?php echo htmlspecialchars($userData['img'] ?: 'default_profile.png'); ?>"
|
||||
alt="avatar"
|
||||
class="rounded-circle img-fluid" style="width: 150px;">
|
||||
<!-- Remove username from here -->
|
||||
<button type="button" class="btn btn-sm btn-secondary mb-2"
|
||||
<?php echo ($editingDisabled) ? 'disabled' : ''; ?>
|
||||
data-bs-toggle="modal" data-bs-target="#profilePictureModal">
|
||||
Change Picture
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- List group for username, email, name, and password -->
|
||||
<div class="list-group mb-4">
|
||||
<div class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<span><?php echo $locale['username']; ?>: <?php echo htmlspecialchars($userData['username']); ?></span>
|
||||
<button type="button" class="btn btn-sm btn-secondary"
|
||||
<?php echo ($editingDisabled) ? 'disabled' : ''; ?>
|
||||
data-bs-toggle="modal" data-bs-target="#usernameModal">
|
||||
Change
|
||||
</button>
|
||||
</div>
|
||||
<div class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<span><?php echo $locale['email']; ?>: <?php echo htmlspecialchars($userData['email']); ?></span>
|
||||
<div>
|
||||
<button type="button" class="btn btn-sm btn-secondary me-1"
|
||||
<?php echo ($editingDisabled) ? 'disabled' : ''; ?>
|
||||
data-bs-toggle="modal" data-bs-target="#emailModal">
|
||||
Change
|
||||
</button>
|
||||
<?php if (!$userData['emailVerified']): ?>
|
||||
<button type="button" class="btn btn-sm btn-primary"
|
||||
data-bs-toggle="modal" data-bs-target="#verifyEmailModal">
|
||||
Verify
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<span><?php echo $locale['name']; ?>: <?php echo htmlspecialchars($userData['firstName'] . ' ' . $userData['lastName']); ?></span>
|
||||
<button type="button" class="btn btn-sm btn-secondary"
|
||||
<?php echo ($editingDisabled) ? 'disabled' : ''; ?>
|
||||
data-bs-toggle="modal" data-bs-target="#nameModal">
|
||||
Change Name
|
||||
</button>
|
||||
</div>
|
||||
<div class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<span>Password</span>
|
||||
<button type="button" class="btn btn-sm btn-secondary"
|
||||
<?php echo ($editingDisabled) ? 'disabled' : ''; ?>
|
||||
data-bs-toggle="modal" data-bs-target="#passwordModal">
|
||||
Change
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Content: Additional Options -->
|
||||
<div class="col-lg-8">
|
||||
<div class="card mb-4">
|
||||
<div class="card-body bg-body-secondary">
|
||||
<h5>Additional Features</h5>
|
||||
<p>Followed DJs and recent ratings will appear here once implemented.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Modals -->
|
||||
<!-- 1. Profile Picture Modal -->
|
||||
<div class="modal fade" id="profilePictureModal" tabindex="-1" aria-labelledby="profilePictureModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<form action="update_profile_picture.php" method="post" enctype="multipart/form-data">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="profilePictureModalLabel">Change Profile Picture</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- CSRF token can be added here as hidden input if needed -->
|
||||
<div class="mb-3">
|
||||
<label for="profilePicture" class="form-label">Select new profile picture</label>
|
||||
<input type="file" class="form-control" id="profilePicture" name="profile_picture" accept="image/*" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Update Picture</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2. Username Modal -->
|
||||
<div class="modal fade" id="usernameModal" tabindex="-1" aria-labelledby="usernameModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<form action="update_username.php" method="post">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="usernameModalLabel">Change Username</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label for="newUsername" class="form-label">New Username</label>
|
||||
<input type="text" class="form-control" id="newUsername" name="new_username" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Update Username</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. Email Modal -->
|
||||
<div class="modal fade" id="emailModal" tabindex="-1" aria-labelledby="emailModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<form action="update_email.php" method="post">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="emailModalLabel">Change Email</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label for="newEmail" class="form-label">New Email Address</label>
|
||||
<input type="email" class="form-control" id="newEmail" name="new_email" required>
|
||||
</div>
|
||||
<p class="text-muted">Note: Changing your email will require you to verify the new address.</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Update Email</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 4. Verify Email Modal -->
|
||||
<div class="modal fade" id="verifyEmailModal" tabindex="-1" aria-labelledby="verifyEmailModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<form action="verify_email.php" method="post">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="verifyEmailModalLabel">Verify Your Email</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>An email with a verification code has been sent to your address. Please enter the code below. (Or click the link in the email to auto-verify.)</p>
|
||||
<div class="mb-3">
|
||||
<label for="verificationCode" class="form-label">Verification Code</label>
|
||||
<input type="text" class="form-control" id="verificationCode" name="verification_code" required>
|
||||
</div>
|
||||
<p class="small text-muted">You can only request a new code once every 15 minutes.</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Verify Email</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 5. Name Modal (First & Last Name) -->
|
||||
<div class="modal fade" id="nameModal" tabindex="-1" aria-labelledby="nameModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<form action="update_name.php" method="post">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="nameModalLabel">Change Your Name</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label for="firstName" class="form-label">First Name</label>
|
||||
<input type="text" class="form-control" id="firstName" name="first_name" value="<?php echo htmlspecialchars($userData['firstName']); ?>" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="lastName" class="form-label">Last Name</label>
|
||||
<input type="text" class="form-control" id="lastName" name="last_name" value="<?php echo htmlspecialchars($userData['lastName']); ?>" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Update Name</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 6. Password Modal -->
|
||||
<div class="modal fade" id="passwordModal" tabindex="-1" aria-labelledby="passwordModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<form action="update_password.php" method="post">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="passwordModalLabel">Change Password</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label for="currentPassword" class="form-label">Current Password</label>
|
||||
<input type="password" class="form-control" id="currentPassword" name="current_password" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="newPassword" class="form-label">New Password</label>
|
||||
<input type="password" class="form-control" id="newPassword" name="new_password" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="confirmPassword" class="form-label">Confirm New Password</label>
|
||||
<input type="password" class="form-control" id="confirmPassword" name="confirm_password" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Change Password</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once 'includes/footer.php'; ?>
|
187
register.php
Normal file
187
register.php
Normal file
|
@ -0,0 +1,187 @@
|
|||
<?php
|
||||
session_start();
|
||||
require_once 'includes/globals.php';
|
||||
require_once 'vendor/autoload.php';
|
||||
|
||||
use DJMixHosting\Database;
|
||||
use Aws\Ses\SesClient;
|
||||
use Aws\Exception\AwsException;
|
||||
|
||||
// If the user is already logged in, redirect them
|
||||
if(isset($_SESSION['user'])) {
|
||||
header("Location: profile.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
if($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||
// Gather form fields
|
||||
$username = trim($_POST['username'] ?? '');
|
||||
$email = trim($_POST['email'] ?? '');
|
||||
$password = $_POST['password'] ?? '';
|
||||
$confirm_password = $_POST['confirm_password'] ?? '';
|
||||
$first_name = trim($_POST['first_name'] ?? '');
|
||||
$last_name = trim($_POST['last_name'] ?? '');
|
||||
|
||||
// Basic validation
|
||||
$errors = [];
|
||||
if(empty($username) || empty($email) || empty($password) || empty($confirm_password) || empty($first_name) || empty($last_name)) {
|
||||
$errors[] = "All fields are required.";
|
||||
}
|
||||
if($password !== $confirm_password) {
|
||||
$errors[] = "Passwords do not match.";
|
||||
}
|
||||
if(!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
$errors[] = "Invalid email format.";
|
||||
}
|
||||
if(!preg_match('/^[a-zA-Z0-9_]{3,25}$/', $username)) {
|
||||
$errors[] = "Invalid username format.";
|
||||
}
|
||||
|
||||
if(!empty($errors)) {
|
||||
$_SESSION['error'] = implode(" ", $errors);
|
||||
header("Location: register.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
$db = new Database($config);
|
||||
|
||||
// Check if username or email already exists
|
||||
$stmt = $db->prepare("SELECT id FROM users WHERE username = ? OR email = ?");
|
||||
$stmt->bind_param("ss", $username, $email);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
if($result->num_rows > 0) {
|
||||
$_SESSION['error'] = "Username or email already exists.";
|
||||
header("Location: register.php");
|
||||
exit;
|
||||
}
|
||||
$stmt->close();
|
||||
|
||||
// Insert the new user record. (Assuming columns firstName and lastName exist.)
|
||||
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
|
||||
$stmt = $db->prepare("INSERT INTO users (username, password, email, firstName, lastName, img, emailVerified) VALUES (?, ?, ?, ?, ?, '', 0)");
|
||||
$stmt->bind_param("sssss", $username, $hashed_password, $email, $first_name, $last_name);
|
||||
if(!$stmt->execute()){
|
||||
$_SESSION['error'] = "Registration failed. Please try again.";
|
||||
header("Location: register.php");
|
||||
exit;
|
||||
}
|
||||
$user_id = $stmt->insert_id;
|
||||
$stmt->close();
|
||||
|
||||
// Log the user in
|
||||
$_SESSION['user'] = [
|
||||
'id' => $user_id,
|
||||
'username' => $username,
|
||||
'email' => $email
|
||||
];
|
||||
|
||||
// Trigger email verification: generate a verification code valid for 15 minutes
|
||||
$verification_code = bin2hex(random_bytes(16));
|
||||
$expires_at = date("Y-m-d H:i:s", strtotime("+15 minutes"));
|
||||
// Insert record with purpose 'email_verification'
|
||||
$stmt = $db->prepare("REPLACE INTO email_verifications (user_id, email, verification_code, expires_at, purpose) VALUES (?, ?, ?, ?, 'email_verification')");
|
||||
$stmt->bind_param("isss", $user_id, $email, $verification_code, $expires_at);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
// Send verification email via AWS SES using config settings
|
||||
$sesClient = new SesClient([
|
||||
'version' => 'latest',
|
||||
'region' => $config['aws']['ses']['region'],
|
||||
'credentials' => [
|
||||
'key' => $config['aws']['ses']['access_key'],
|
||||
'secret' => $config['aws']['ses']['secret_key']
|
||||
]
|
||||
]);
|
||||
$sender_email = $config['aws']['ses']['sender_email'];
|
||||
$recipient_email = $email;
|
||||
$subject = "Verify Your Email Address";
|
||||
$verification_link = $config['app']['url'] . "/verify_email.php?code={$verification_code}";
|
||||
$body_text = "Thank you for registering at " . $config['app']['name'] . ".\n\n";
|
||||
$body_text .= "Please verify your email address by clicking the link below or by entering the verification code in your profile:\n\n";
|
||||
$body_text .= "{$verification_link}\n\nYour verification code is: {$verification_code}\nThis code will expire in 15 minutes.";
|
||||
|
||||
try {
|
||||
$result = $sesClient->sendEmail([
|
||||
'Destination' => [
|
||||
'ToAddresses' => [$recipient_email],
|
||||
],
|
||||
'ReplyToAddresses' => [$sender_email],
|
||||
'Source' => $sender_email,
|
||||
'Message' => [
|
||||
'Body' => [
|
||||
'Text' => [
|
||||
'Charset' => 'UTF-8',
|
||||
'Data' => $body_text,
|
||||
],
|
||||
],
|
||||
'Subject' => [
|
||||
'Charset' => 'UTF-8',
|
||||
'Data' => $subject,
|
||||
],
|
||||
],
|
||||
]);
|
||||
$_SESSION['success'] = "Registration successful! A verification email has been sent to your email address.";
|
||||
} catch (AwsException $e) {
|
||||
$_SESSION['error'] = "Registration successful, but failed to send verification email: " . $e->getAwsErrorMessage();
|
||||
}
|
||||
|
||||
header("Location: profile.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once 'includes/header.php';
|
||||
?>
|
||||
|
||||
<section class="register-section py-5">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-6">
|
||||
<?php
|
||||
if(isset($_SESSION['error'])) {
|
||||
echo '<div class="alert alert-danger alert-dismissible fade show mb-4" role="alert">' . htmlspecialchars($_SESSION['error']) . '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
|
||||
unset($_SESSION['error']);
|
||||
}
|
||||
?>
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<h3 class="text-center mb-4">Register</h3>
|
||||
<form action="register.php" method="post" class="needs-validation" novalidate>
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Username</label>
|
||||
<input type="text" class="form-control" id="username" name="username" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">Email</label>
|
||||
<input type="email" class="form-control" id="email" name="email" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="firstName" class="form-label">First Name</label>
|
||||
<input type="text" class="form-control" id="firstName" name="first_name" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="lastName" class="form-label">Last Name</label>
|
||||
<input type="text" class="form-control" id="lastName" name="last_name" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="confirm_password" class="form-label">Confirm Password</label>
|
||||
<input type="password" class="form-control" id="confirm_password" name="confirm_password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100">Register</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center mt-4">
|
||||
<p class="mb-0">Already have an account? <a href="login.php" class="text-decoration-none">Login</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php require_once 'includes/footer.php'; ?>
|
97
update_email.php
Normal file
97
update_email.php
Normal file
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
session_start();
|
||||
require_once 'includes/globals.php';
|
||||
require_once 'vendor/autoload.php';
|
||||
|
||||
use DJMixHosting\Database;
|
||||
use Aws\Ses\SesClient;
|
||||
use Aws\Exception\AwsException;
|
||||
|
||||
if (!isset($_SESSION['user'])) {
|
||||
header("Location: login.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
header("Location: profile.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!isset($_POST['new_email']) || empty($_POST['new_email'])) {
|
||||
$_SESSION['error'] = "New email address is required.";
|
||||
header("Location: profile.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
$new_email = filter_var($_POST['new_email'], FILTER_VALIDATE_EMAIL);
|
||||
if (!$new_email) {
|
||||
$_SESSION['error'] = "Invalid email format.";
|
||||
header("Location: profile.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
$db = new Database($config);
|
||||
$userId = $_SESSION['user']['id'];
|
||||
|
||||
// Update the user's email and mark it as unverified
|
||||
$stmt = $db->prepare("UPDATE users SET email = ?, emailVerified = 0 WHERE id = ?");
|
||||
$stmt->bind_param("si", $new_email, $userId);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
// Generate verification code and expiry (15 minutes from now)
|
||||
$verification_code = bin2hex(random_bytes(16));
|
||||
$expires_at = date("Y-m-d H:i:s", strtotime("+15 minutes"));
|
||||
|
||||
// Store the verification record (using REPLACE to update any existing record for this user and email)
|
||||
$stmt = $db->prepare("REPLACE INTO email_verifications (user_id, email, verification_code, expires_at) VALUES (?, ?, ?, ?)");
|
||||
$stmt->bind_param("isss", $userId, $new_email, $verification_code, $expires_at);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
// Send email using AWS SES with config settings
|
||||
$sesClient = new SesClient([
|
||||
'version' => 'latest',
|
||||
'region' => $config['aws']['ses']['region'],
|
||||
'credentials' => [
|
||||
'key' => $config['aws']['ses']['access_key'],
|
||||
'secret' => $config['aws']['ses']['secret_key'],
|
||||
]
|
||||
]);
|
||||
|
||||
$sender_email = $config['aws']['ses']['sender_email'];
|
||||
$recipient_email = $new_email;
|
||||
$subject = "Verify Your Email Address";
|
||||
|
||||
// Construct a verification link. Users can click this link to auto-submit the code.
|
||||
$verification_link = $config['app']['url'] . "/verify_email.php?code={$verification_code}";
|
||||
$body_text = "Please verify your email address by clicking the link below or by entering the code in your profile:\n\n";
|
||||
$body_text .= "{$verification_link}\n\nYour verification code is: {$verification_code}\nThis code will expire in 15 minutes.";
|
||||
|
||||
try {
|
||||
$result = $sesClient->sendEmail([
|
||||
'Destination' => [
|
||||
'ToAddresses' => [$recipient_email],
|
||||
],
|
||||
'ReplyToAddresses' => [$sender_email],
|
||||
'Source' => $sender_email,
|
||||
'Message' => [
|
||||
'Body' => [
|
||||
'Text' => [
|
||||
'Charset' => 'UTF-8',
|
||||
'Data' => $body_text,
|
||||
],
|
||||
],
|
||||
'Subject' => [
|
||||
'Charset' => 'UTF-8',
|
||||
'Data' => $subject,
|
||||
],
|
||||
],
|
||||
]);
|
||||
$_SESSION['success'] = "Email updated. A verification email has been sent to your new address.";
|
||||
} catch (AwsException $e) {
|
||||
$_SESSION['error'] = "Failed to send verification email: " . $e->getAwsErrorMessage();
|
||||
}
|
||||
|
||||
header("Location: profile.php");
|
||||
exit;
|
43
update_name.php
Normal file
43
update_name.php
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
session_start();
|
||||
require_once 'includes/globals.php';
|
||||
require_once 'vendor/autoload.php';
|
||||
|
||||
use DJMixHosting\Database;
|
||||
|
||||
// Ensure the user is logged in
|
||||
if (!isset($_SESSION['user'])) {
|
||||
header("Location: login.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Check that both first and last names were provided
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || empty($_POST['first_name']) || empty($_POST['last_name'])) {
|
||||
$_SESSION['error'] = "Both first name and last name are required.";
|
||||
header("Location: profile.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
$firstName = trim($_POST['first_name']);
|
||||
$lastName = trim($_POST['last_name']);
|
||||
|
||||
$db = new Database($config);
|
||||
$userId = $_SESSION['user']['id'];
|
||||
|
||||
// Update the user's first and last name in the database
|
||||
$stmt = $db->prepare("UPDATE users SET firstName = ?, lastName = ? WHERE id = ?");
|
||||
$stmt->bind_param("ssi", $firstName, $lastName, $userId);
|
||||
if (!$stmt->execute()) {
|
||||
$_SESSION['error'] = "Failed to update name. Please try again.";
|
||||
header("Location: profile.php");
|
||||
exit;
|
||||
}
|
||||
$stmt->close();
|
||||
|
||||
// Optionally update session data if you store these fields there
|
||||
$_SESSION['user']['firstName'] = $firstName;
|
||||
$_SESSION['user']['lastName'] = $lastName;
|
||||
|
||||
$_SESSION['success'] = "Name updated successfully.";
|
||||
header("Location: profile.php");
|
||||
exit;
|
84
update_password.php
Normal file
84
update_password.php
Normal file
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
session_start();
|
||||
require_once 'includes/globals.php';
|
||||
require_once 'vendor/autoload.php';
|
||||
|
||||
use DJMixHosting\Database;
|
||||
|
||||
// Ensure the user is authenticated.
|
||||
if (!isset($_SESSION['user'])) {
|
||||
header("Location: login.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Check for required POST fields.
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || empty($_POST['current_password']) || empty($_POST['new_password']) || empty($_POST['confirm_password'])) {
|
||||
$_SESSION['error'] = "All password fields are required.";
|
||||
header("Location: profile.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
$current_password = $_POST['current_password'];
|
||||
$new_password = $_POST['new_password'];
|
||||
$confirm_password = $_POST['confirm_password'];
|
||||
|
||||
// Validate that the new password meets the requirements:
|
||||
// - 8 to 32 characters
|
||||
// - at least one uppercase letter
|
||||
// - at least one lowercase letter
|
||||
// - at least one number
|
||||
// - at least one symbol
|
||||
$pattern = '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_]).{8,32}$/';
|
||||
if (!preg_match($pattern, $new_password)) {
|
||||
$_SESSION['error'] = "New password must be 8-32 characters and include at least one uppercase letter, one lowercase letter, one number, and one symbol.";
|
||||
header("Location: profile.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Verify that new password and confirmation match.
|
||||
if ($new_password !== $confirm_password) {
|
||||
$_SESSION['error'] = "New password and confirmation do not match.";
|
||||
header("Location: profile.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
$db = new Database($config);
|
||||
$userId = $_SESSION['user']['id'];
|
||||
|
||||
// Retrieve the current password hash from the database.
|
||||
$stmt = $db->prepare("SELECT password FROM users WHERE id = ?");
|
||||
$stmt->bind_param("i", $userId);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
$userData = $result->fetch_assoc();
|
||||
$stmt->close();
|
||||
|
||||
if (!$userData) {
|
||||
$_SESSION['error'] = "User not found.";
|
||||
header("Location: profile.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Verify that the current password is correct.
|
||||
if (!password_verify($current_password, $userData['password'])) {
|
||||
$_SESSION['error'] = "Current password is incorrect.";
|
||||
header("Location: profile.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Hash the new password.
|
||||
$hashed_new_password = password_hash($new_password, PASSWORD_DEFAULT);
|
||||
|
||||
// Update the user's password in the database.
|
||||
$stmt = $db->prepare("UPDATE users SET password = ? WHERE id = ?");
|
||||
$stmt->bind_param("si", $hashed_new_password, $userId);
|
||||
if (!$stmt->execute()) {
|
||||
$_SESSION['error'] = "Failed to update password. Please try again.";
|
||||
header("Location: profile.php");
|
||||
exit;
|
||||
}
|
||||
$stmt->close();
|
||||
|
||||
$_SESSION['success'] = "Password updated successfully.";
|
||||
header("Location: profile.php");
|
||||
exit;
|
53
update_username.php
Normal file
53
update_username.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
session_start();
|
||||
require_once 'includes/globals.php';
|
||||
require_once 'vendor/autoload.php';
|
||||
|
||||
use DJMixHosting\Database;
|
||||
|
||||
if (!isset($_SESSION['user'])) {
|
||||
header("Location: login.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['new_username']) || empty($_POST['new_username'])) {
|
||||
$_SESSION['error'] = "New username is required.";
|
||||
header("Location: profile.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
$new_username = trim($_POST['new_username']);
|
||||
|
||||
// Validate username (for example, only alphanumeric and underscores, 3-25 characters)
|
||||
if (!preg_match('/^[a-zA-Z0-9_]{3,25}$/', $new_username)) {
|
||||
$_SESSION['error'] = "Invalid username format.";
|
||||
header("Location: profile.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
$db = new Database($config);
|
||||
$userId = $_SESSION['user']['id'];
|
||||
|
||||
// Check if the new username already exists (excluding the current user)
|
||||
$stmt = $db->prepare("SELECT id FROM users WHERE username = ? AND id != ?");
|
||||
$stmt->bind_param("si", $new_username, $userId);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
if ($result->num_rows > 0) {
|
||||
$_SESSION['error'] = "Username already taken.";
|
||||
header("Location: profile.php");
|
||||
exit;
|
||||
}
|
||||
$stmt->close();
|
||||
|
||||
// Update the username in the database
|
||||
$stmt = $db->prepare("UPDATE users SET username = ? WHERE id = ?");
|
||||
$stmt->bind_param("si", $new_username, $userId);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
// Update session data
|
||||
$_SESSION['user']['username'] = $new_username;
|
||||
$_SESSION['success'] = "Username updated successfully.";
|
||||
header("Location: profile.php");
|
||||
exit;
|
149
upload.php
Normal file
149
upload.php
Normal file
|
@ -0,0 +1,149 @@
|
|||
<?php
|
||||
// upload.php - Step 1: File upload and immediate processing
|
||||
|
||||
require_once 'includes/globals.php';
|
||||
|
||||
// Ensure user is authenticated
|
||||
if (!isset($_SESSION['user'])) {
|
||||
header("Location: login.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Process the form submission
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['mix_file'])) {
|
||||
// Get upload config from $config
|
||||
$tmpPath = rtrim($config['uploads']['tmp_path'], '/') . '/';
|
||||
$maxFileSize = $config['uploads']['max_file_size'];
|
||||
$allowedMimeTypes = $config['uploads']['allowed_mime_type'];
|
||||
$allowedFileTypes = $config['uploads']['allowed_file_types'];
|
||||
|
||||
$file = $_FILES['mix_file'];
|
||||
|
||||
// Basic file validations
|
||||
if ($file['error'] !== UPLOAD_ERR_OK) {
|
||||
$_SESSION['error'] = "File upload error.";
|
||||
header("Location: upload.php");
|
||||
exit;
|
||||
}
|
||||
if ($file['size'] > $maxFileSize) {
|
||||
$_SESSION['error'] = "File is too large.";
|
||||
header("Location: upload.php");
|
||||
exit;
|
||||
}
|
||||
// Get file extension and mime type
|
||||
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
|
||||
if (!in_array($ext, $allowedFileTypes)) {
|
||||
$_SESSION['error'] = "File type not allowed.";
|
||||
header("Location: upload.php");
|
||||
exit;
|
||||
}
|
||||
if (!in_array($file['type'], $allowedMimeTypes)) {
|
||||
$_SESSION['error'] = "MIME type not allowed.";
|
||||
header("Location: upload.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Create a unique temporary filename
|
||||
$uniqueName = uniqid() . "." . $ext;
|
||||
$destination = $tmpPath . $uniqueName;
|
||||
|
||||
if (!move_uploaded_file($file['tmp_name'], $destination)) {
|
||||
$_SESSION['error'] = "Failed to save uploaded file.";
|
||||
header("Location: upload.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Process the file: if mp3, extract metadata; if zip, extract each mp3's metadata.
|
||||
$uploadTask = [];
|
||||
$uploadTask['original_name'] = $file['name'];
|
||||
$uploadTask['local_path'] = $destination;
|
||||
$uploadTask['size'] = $file['size'];
|
||||
$uploadTask['ext'] = $ext;
|
||||
|
||||
if ($ext === "mp3") {
|
||||
// Process MP3 file using shell_exec calls (ensure you sanitize arguments)
|
||||
$escapedFile = escapeshellarg($destination);
|
||||
$artist = trim(shell_exec("eyed3 --query='%a%' $escapedFile"));
|
||||
$title = trim(shell_exec("eyed3 --query='%t%' $escapedFile"));
|
||||
$duration = trim(shell_exec("mp3info -p \"%S\" $escapedFile"));
|
||||
// You can extract additional info as needed
|
||||
$uploadTask['file_type'] = 'mp3';
|
||||
$uploadTask['metadata'] = [
|
||||
'artist' => $artist,
|
||||
'title' => $title,
|
||||
'duration' => $duration, // in seconds
|
||||
];
|
||||
$uploadTask['mediaplayer'] = 1;
|
||||
} elseif ($ext === "zip") {
|
||||
// Process ZIP file using ZipArchive
|
||||
$zip = new ZipArchive;
|
||||
if ($zip->open($destination) === true) {
|
||||
$totalDuration = 0;
|
||||
$tracklist = [];
|
||||
for ($i = 0; $i < $zip->numFiles; $i++) {
|
||||
$entryName = $zip->getNameIndex($i);
|
||||
$entryExt = strtolower(pathinfo($entryName, PATHINFO_EXTENSION));
|
||||
if ($entryExt === "mp3") {
|
||||
// Extract the MP3 temporarily to process metadata
|
||||
$tempDir = $tmpPath . uniqid('zip_');
|
||||
mkdir($tempDir);
|
||||
$tempFilePath = $tempDir . '/' . basename($entryName);
|
||||
$zip->extractTo($tempDir, $entryName);
|
||||
$escapedFile = escapeshellarg($tempFilePath);
|
||||
$title = trim(shell_exec("eyed3 --query='%t%' $escapedFile"));
|
||||
$duration = trim(shell_exec("mp3info -p \"%S\" $escapedFile"));
|
||||
$tracklist[] = $title ?: basename($entryName);
|
||||
$totalDuration += (int)$duration;
|
||||
unlink($tempFilePath);
|
||||
rmdir($tempDir);
|
||||
}
|
||||
}
|
||||
$zip->close();
|
||||
$uploadTask['file_type'] = 'zip';
|
||||
$uploadTask['metadata'] = [
|
||||
'tracklist' => $tracklist,
|
||||
'total_duration' => $totalDuration,
|
||||
];
|
||||
// Mark ZIPs as download only (no mediaplayer)
|
||||
$uploadTask['mediaplayer'] = 0;
|
||||
} else {
|
||||
$_SESSION['error'] = "Failed to open ZIP file.";
|
||||
header("Location: upload.php");
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
$_SESSION['error'] = "Unsupported file type.";
|
||||
header("Location: upload.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Save the upload task details in session so step 2 can use them.
|
||||
$_SESSION['upload_task'] = $uploadTask;
|
||||
header("Location: upload_details.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once 'includes/header.php';
|
||||
?>
|
||||
|
||||
<section class="upload-section py-5">
|
||||
<div class="container">
|
||||
<h2 class="mb-4">Upload a New Mix</h2>
|
||||
<?php
|
||||
if (isset($_SESSION['error'])) {
|
||||
echo '<div class="alert alert-danger">' . htmlspecialchars($_SESSION['error']) . '</div>';
|
||||
unset($_SESSION['error']);
|
||||
}
|
||||
?>
|
||||
<form action="upload.php" method="post" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<label for="mix_file" class="form-label">Select Mix File (MP3 or ZIP)</label>
|
||||
<input type="file" class="form-control" id="mix_file" name="mix_file" accept=".mp3,.zip" required>
|
||||
</div>
|
||||
<!-- Optionally, add album art upload here later -->
|
||||
<button type="submit" class="btn btn-primary">Upload File</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php require_once 'includes/footer.php'; ?>
|
245
upload_details.php
Normal file
245
upload_details.php
Normal file
|
@ -0,0 +1,245 @@
|
|||
<?php
|
||||
// upload_details.php - Step 2: Enter mix details and finalize upload
|
||||
|
||||
require_once 'includes/globals.php';
|
||||
require_once 'vendor/autoload.php';
|
||||
|
||||
use DJMixHosting\Database;
|
||||
use DJMixHosting\Genres;
|
||||
use DJMixHosting\DJs;
|
||||
use Aws\Credentials\Credentials;
|
||||
|
||||
// Ensure user is authenticated and an upload task exists
|
||||
if (!isset($_SESSION['user']) || !isset($_SESSION['upload_task'])) {
|
||||
header("Location: upload.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
$uploadTask = $_SESSION['upload_task'];
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// Get form fields
|
||||
$title = trim($_POST['title'] ?? '');
|
||||
$description = trim($_POST['description'] ?? '');
|
||||
$recorded = trim($_POST['recorded'] ?? '');
|
||||
$selectedGenres = $_POST['genres'] ?? []; // an array of genre IDs or names
|
||||
$dj1 = $_POST['dj1'] ?? 0;
|
||||
$dj2 = $_POST['dj2'] ?? 0;
|
||||
$dj3 = $_POST['dj3'] ?? 0;
|
||||
|
||||
// Basic validation
|
||||
if (empty($title)) {
|
||||
$_SESSION['error'] = "Mix title is required.";
|
||||
header("Location: upload_details.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Generate a slug from the title
|
||||
function slugify($text) {
|
||||
// Replace non-letter or digits by -
|
||||
$text = preg_replace('~[^\pL\d]+~u', '-', $text);
|
||||
// Transliterate
|
||||
$text = iconv('utf-8', 'us-ascii//TRANSLIT', $text);
|
||||
// Remove unwanted characters
|
||||
$text = preg_replace('~[^-\w]+~', '', $text);
|
||||
// Trim
|
||||
$text = trim($text, '-');
|
||||
// Remove duplicate -
|
||||
$text = preg_replace('~-+~', '-', $text);
|
||||
// Lowercase
|
||||
$text = strtolower($text);
|
||||
return empty($text) ? 'n-a' : $text;
|
||||
}
|
||||
$slug = slugify($title);
|
||||
|
||||
|
||||
|
||||
$credentials = new Credentials(
|
||||
$config['aws']['s3']['access_key'],
|
||||
$config['aws']['s3']['secret_key']
|
||||
);
|
||||
|
||||
$s3Client = new Aws\S3\S3Client([
|
||||
'version' => 'latest',
|
||||
'region' => $config['aws']['s3']['region'],
|
||||
'endpoint' => $config['aws']['s3']['host'],
|
||||
'credentials' => $credentials,
|
||||
'use_path_style_endpoint' => true,
|
||||
'profile' => null, // disable reading ~/.aws/config
|
||||
'use_aws_shared_config_files' => false,
|
||||
]);
|
||||
|
||||
// Determine remote file path – for now, store in a temporary folder on Spaces
|
||||
$remotePath = "temp/mixes/" . uniqid() . "_" . basename($uploadTask['local_path']);
|
||||
|
||||
// Determine MIME type from file extension
|
||||
$mimeType = ($uploadTask['ext'] === 'mp3') ? 'audio/mpeg' : 'application/zip';
|
||||
|
||||
try {
|
||||
$s3Client->putObject([
|
||||
'Bucket' => $config['aws']['s3']['bucket'],
|
||||
'Key' => $remotePath,
|
||||
'SourceFile' => $uploadTask['local_path'],
|
||||
'ACL' => 'private', // file remains hidden until approved
|
||||
'ContentType' => $mimeType,
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
$_SESSION['error'] = "Error uploading file to CDN: " . $e->getMessage();
|
||||
header("Location: upload_details.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Now insert a new mix record into the database with pending status.
|
||||
$db = new Database($config);
|
||||
$stmt = $db->prepare("INSERT INTO mix (title, slug, description, cover, url, seconds, mediaplayer, dj1, dj2, dj3, pending, recorded) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?)");
|
||||
|
||||
// Determine the duration and mediaplayer flag from the upload task:
|
||||
// For an MP3 file, set mediaplayer to 0; for a ZIP file, set it to 1.
|
||||
$seconds = 0;
|
||||
if ($uploadTask['file_type'] === 'mp3') {
|
||||
$seconds = (int)$uploadTask['metadata']['duration'];
|
||||
$mediaplayer = 0; // MP3 gets a player
|
||||
} elseif ($uploadTask['file_type'] === 'zip') {
|
||||
$seconds = (int)$uploadTask['metadata']['total_duration'];
|
||||
$mediaplayer = 1; // ZIPs are download only, so flag them as 1
|
||||
}
|
||||
// The URL can be constructed as per your CDN structure; for now, we store the remote path.
|
||||
$url = $remotePath;
|
||||
|
||||
// For DJs, cast to integer (0 if none selected)
|
||||
$dj1 = (int)$dj1;
|
||||
$dj2 = (int)$dj2;
|
||||
$dj3 = (int)$dj3;
|
||||
$cover = "";
|
||||
$stmt->bind_param("sssssiiiiss", $title, $slug, $description, $cover, $url, $seconds, $mediaplayer, $dj1, $dj2, $dj3, $recorded);
|
||||
if (!$stmt->execute()) {
|
||||
$_SESSION['error'] = "Error saving mix to database.";
|
||||
header("Location: upload_details.php");
|
||||
exit;
|
||||
}
|
||||
$mixId = $stmt->insert_id;
|
||||
$stmt->close();
|
||||
|
||||
// Insert mix_meta entries for genres and tracklist
|
||||
// For genres, assume $selectedGenres is an array of genre IDs (or names, if new genres need to be added)
|
||||
foreach ($selectedGenres as $genreId) {
|
||||
$stmt = $db->prepare("INSERT INTO mix_meta (mix_id, attribute, value) VALUES (?, 'genre', ?)");
|
||||
$stmt->bind_param("is", $mixId, $genreId);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
}
|
||||
// If the file was mp3 and metadata includes a track title or tracklist, insert that as well:
|
||||
if ($uploadTask['file_type'] === 'mp3' && !empty($uploadTask['metadata']['title'])) {
|
||||
$tracklist = $uploadTask['metadata']['title'];
|
||||
$stmt = $db->prepare("INSERT INTO mix_meta (mix_id, attribute, value) VALUES (?, 'tracklist', ?)");
|
||||
$stmt->bind_param("is", $mixId, $tracklist);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
} elseif ($uploadTask['file_type'] === 'zip' && !empty($uploadTask['metadata']['tracklist'])) {
|
||||
// If multiple tracks, you might store them as a newline-separated string
|
||||
$tracklist = implode("\n", $uploadTask['metadata']['tracklist']);
|
||||
$stmt = $db->prepare("INSERT INTO mix_meta (mix_id, attribute, value) VALUES (?, 'tracklist', ?)");
|
||||
$stmt->bind_param("is", $mixId, $tracklist);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
}
|
||||
|
||||
// Cleanup: delete the local temporary file and clear session task
|
||||
unlink($uploadTask['local_path']);
|
||||
unset($_SESSION['upload_task']);
|
||||
|
||||
$_SESSION['success'] = "Mix uploaded successfully and is pending approval.";
|
||||
header("Location: profile.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once 'includes/header.php';
|
||||
|
||||
// Load available genres and DJs for the form
|
||||
$db = new Database($config);
|
||||
$genresObj = new DJMixHosting\Genres($db);
|
||||
$allGenres = $genresObj->get_all_genres();
|
||||
$djsObj = new DJMixHosting\DJs($db);
|
||||
$allDJs = $djsObj->get_all_djs();
|
||||
|
||||
?>
|
||||
|
||||
<section class="upload-details-section py-5">
|
||||
<div class="container">
|
||||
<h2 class="mb-4">Enter Mix Details</h2>
|
||||
<?php
|
||||
if(isset($_SESSION['error'])) {
|
||||
echo '<div class="alert alert-danger">' . htmlspecialchars($_SESSION['error']) . '</div>';
|
||||
unset($_SESSION['error']);
|
||||
}
|
||||
?>
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5>File Summary</h5>
|
||||
<p><strong>Original Name:</strong> <?php echo htmlspecialchars($uploadTask['original_name']); ?></p>
|
||||
<p><strong>File Type:</strong> <?php echo htmlspecialchars(strtoupper($uploadTask['file_type'])); ?></p>
|
||||
<p><strong>Size:</strong> <?php echo round($uploadTask['size'] / 1024, 2); ?> KB</p>
|
||||
<?php if ($uploadTask['file_type'] === 'mp3'): ?>
|
||||
<p><strong>Duration:</strong> <?php echo htmlspecialchars($uploadTask['metadata']['duration']); ?> seconds</p>
|
||||
<?php elseif ($uploadTask['file_type'] === 'zip'): ?>
|
||||
<p><strong>Total Duration:</strong> <?php echo htmlspecialchars($uploadTask['metadata']['total_duration']); ?> seconds</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form action="upload_details.php" method="post" class="needs-validation" novalidate>
|
||||
<div class="mb-3">
|
||||
<label for="title" class="form-label">Mix Title</label>
|
||||
<input type="text" class="form-control" id="title" name="title" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="description" class="form-label">Mix Description</label>
|
||||
<textarea class="form-control" id="description" name="description" rows="3"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="recorded" class="form-label">Recorded Date</label>
|
||||
<input type="date" class="form-control" id="recorded" name="recorded" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="genres" class="form-label">Select Genres (type to search)</label>
|
||||
<select class="form-select" id="genres" name="genres[]" multiple required>
|
||||
<?php foreach ($allGenres as $genre): ?>
|
||||
<option value="<?php echo htmlspecialchars($genre['id']); ?>"><?php echo htmlspecialchars($genre['name']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Select DJs (Maximum 3)</label>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<select class="form-select" name="dj1" required>
|
||||
<option value="">Select DJ 1</option>
|
||||
<?php foreach ($allDJs as $dj): ?>
|
||||
<option value="<?php echo htmlspecialchars($dj['id']); ?>"><?php echo htmlspecialchars($dj['name']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col">
|
||||
<select class="form-select" name="dj2">
|
||||
<option value="">Select DJ 2 (Optional)</option>
|
||||
<?php foreach ($allDJs as $dj): ?>
|
||||
<option value="<?php echo htmlspecialchars($dj['id']); ?>"><?php echo htmlspecialchars($dj['name']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col">
|
||||
<select class="form-select" name="dj3">
|
||||
<option value="">Select DJ 3 (Optional)</option>
|
||||
<?php foreach ($allDJs as $dj): ?>
|
||||
<option value="<?php echo htmlspecialchars($dj['id']); ?>"><?php echo htmlspecialchars($dj['name']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Submit Mix</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php require_once 'includes/footer.php'; ?>
|
65
verify_email.php
Normal file
65
verify_email.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
session_start();
|
||||
require_once 'includes/globals.php';
|
||||
require_once 'vendor/autoload.php';
|
||||
|
||||
use DJMixHosting\Database;
|
||||
|
||||
if (!isset($_SESSION['user'])) {
|
||||
header("Location: login.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
$db = new Database($config);
|
||||
$userId = $_SESSION['user']['id'];
|
||||
|
||||
// Retrieve the verification code from GET or POST
|
||||
$verification_code = "";
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['verification_code'])) {
|
||||
$verification_code = trim($_POST['verification_code']);
|
||||
} elseif (isset($_GET['code'])) {
|
||||
$verification_code = trim($_GET['code']);
|
||||
} else {
|
||||
$_SESSION['error'] = "Verification code is required.";
|
||||
header("Location: profile.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Look up the email verification record for this user and code
|
||||
$stmt = $db->prepare("SELECT * FROM email_verifications WHERE user_id = ? AND verification_code = ?");
|
||||
$stmt->bind_param("is", $userId, $verification_code);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
$record = $result->fetch_assoc();
|
||||
$stmt->close();
|
||||
|
||||
if (!$record) {
|
||||
$_SESSION['error'] = "Invalid verification code.";
|
||||
header("Location: profile.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Check if the verification code has expired
|
||||
$current_time = new DateTime();
|
||||
$expires_at = new DateTime($record['expires_at']);
|
||||
if ($current_time > $expires_at) {
|
||||
$_SESSION['error'] = "Verification code has expired. Please request a new one.";
|
||||
header("Location: profile.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Verification successful: update the user's record
|
||||
$stmt = $db->prepare("UPDATE users SET emailVerified = 1 WHERE id = ?");
|
||||
$stmt->bind_param("i", $userId);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
// Remove the verification record for cleanup
|
||||
$stmt = $db->prepare("DELETE FROM email_verifications WHERE user_id = ?");
|
||||
$stmt->bind_param("i", $userId);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
$_SESSION['success'] = "Email verified successfully.";
|
||||
header("Location: profile.php");
|
||||
exit;
|
Loading…
Add table
Add a link
Reference in a new issue