Language updates. New upload form. new classes.
This commit is contained in:
parent
4c2857b445
commit
8f3061ab99
62 changed files with 3107 additions and 1883 deletions
75
classes/CDN.php
Normal file
75
classes/CDN.php
Normal file
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
namespace DJMixHosting;
|
||||
|
||||
use Aws\S3\S3Client;
|
||||
use Aws\Credentials\Credentials;
|
||||
|
||||
class CDN
|
||||
{
|
||||
protected $s3Client;
|
||||
protected $bucket;
|
||||
protected $config;
|
||||
|
||||
public function __construct(array $config)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->bucket = $config['aws']['s3']['bucket'];
|
||||
|
||||
$credentials = new Credentials(
|
||||
$config['aws']['s3']['access_key'],
|
||||
$config['aws']['s3']['secret_key']
|
||||
);
|
||||
|
||||
$this->s3Client = new S3Client([
|
||||
'version' => 'latest',
|
||||
'region' => $config['aws']['s3']['region'],
|
||||
'endpoint' => $config['aws']['s3']['host'],
|
||||
'credentials' => $credentials,
|
||||
'use_path_style_endpoint' => true,
|
||||
'profile' => null,
|
||||
'use_aws_shared_config_files' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function uploadFile(string $localPath, string $remotePath, string $mimeType, string $acl = 'private')
|
||||
{
|
||||
try {
|
||||
$result = $this->s3Client->putObject([
|
||||
'Bucket' => $this->bucket,
|
||||
'Key' => $remotePath,
|
||||
'SourceFile' => $localPath,
|
||||
'ACL' => $acl,
|
||||
'ContentType' => $mimeType,
|
||||
]);
|
||||
return $result;
|
||||
} catch (\Exception $e) {
|
||||
throw new \Exception("Error uploading file to CDN: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function renameFile(string $oldRemotePath, string $newRemotePath)
|
||||
{
|
||||
// S3 does not support renaming directly. Copy then delete.
|
||||
try {
|
||||
$this->s3Client->copyObject([
|
||||
'Bucket' => $this->bucket,
|
||||
'CopySource' => "{$this->bucket}/{$oldRemotePath}",
|
||||
'Key' => $newRemotePath,
|
||||
]);
|
||||
|
||||
$this->s3Client->deleteObject([
|
||||
'Bucket' => $this->bucket,
|
||||
'Key' => $oldRemotePath,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
throw new \Exception("Error renaming file on CDN: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function clearCache(string $remotePath)
|
||||
{
|
||||
// If using CloudFront or another CDN in front of S3, implement invalidation logic here.
|
||||
// This might involve calling the CloudFront API to invalidate the specific path.
|
||||
}
|
||||
}
|
73
classes/Email.php
Normal file
73
classes/Email.php
Normal file
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
namespace DJMixHosting;
|
||||
|
||||
use Aws\Ses\SesClient;
|
||||
use Aws\Exception\AwsException;
|
||||
use Exception;
|
||||
|
||||
class Email {
|
||||
|
||||
private $config;
|
||||
private $sesClient;
|
||||
|
||||
public function __construct(array $config) {
|
||||
$this->config = $config;
|
||||
$this->sesClient = new SesClient([
|
||||
'version' => 'latest',
|
||||
'region' => $config['aws']['ses']['region'],
|
||||
'credentials' => [
|
||||
'key' => $config['aws']['ses']['access_key'],
|
||||
'secret' => $config['aws']['ses']['secret_key']
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic method to send an email.
|
||||
*
|
||||
* @param string $recipientEmail
|
||||
* @param string $subject
|
||||
* @param string $bodyText
|
||||
* @throws Exception if email sending fails.
|
||||
*/
|
||||
public function sendEmail(string $recipientEmail, string $subject, string $bodyText): void {
|
||||
$senderEmail = $this->config['aws']['ses']['sender_email'];
|
||||
try {
|
||||
$this->sesClient->sendEmail([
|
||||
'Destination' => ['ToAddresses' => [$recipientEmail]],
|
||||
'ReplyToAddresses' => [$senderEmail],
|
||||
'Source' => $senderEmail,
|
||||
'Message' => [
|
||||
'Body' => [
|
||||
'Text' => [
|
||||
'Charset' => 'UTF-8',
|
||||
'Data' => $bodyText,
|
||||
],
|
||||
],
|
||||
'Subject' => [
|
||||
'Charset' => 'UTF-8',
|
||||
'Data' => $subject,
|
||||
],
|
||||
],
|
||||
]);
|
||||
} catch (AwsException $e) {
|
||||
throw new Exception("Failed to send email: " . $e->getAwsErrorMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to send a verification email.
|
||||
*
|
||||
* @param string $recipientEmail
|
||||
* @param string $verificationCode
|
||||
* @throws Exception if email sending fails.
|
||||
*/
|
||||
public function sendVerificationEmail(string $recipientEmail, string $verificationCode): void {
|
||||
$subject = "Verify Your Email Address";
|
||||
$verificationLink = $this->config['app']['url'] . "/verify_email.php?code={$verificationCode}";
|
||||
$bodyText = "Please verify your email address by clicking the link below or by entering the code in your profile:\n\n";
|
||||
$bodyText .= "{$verificationLink}\n\nYour verification code is: {$verificationCode}\nThis code will expire in 15 minutes.";
|
||||
$this->sendEmail($recipientEmail, $subject, $bodyText);
|
||||
}
|
||||
}
|
|
@ -26,29 +26,29 @@ class HeaderMeta
|
|||
$this->config = $config;
|
||||
// detect if it is DJMixHosting\Mix, DJMixHosting\Genre, DJMixHosting\Mixshow, or DJMixHosting\DJ in $object
|
||||
if ($object instanceof Mix) {
|
||||
$djs = $object->get_djs();
|
||||
$djs = $object->getDJs();
|
||||
$djList = "";
|
||||
foreach ($djs as $dj) {
|
||||
$djList .= $dj->getName() . ", ";
|
||||
}
|
||||
$djList = rtrim($djList, ", ");
|
||||
|
||||
$genres = $object->get_genres();
|
||||
$genres = $object->getGenres();
|
||||
$genreNames = "";
|
||||
|
||||
$genreList = rtrim($genreNames, ", ");
|
||||
|
||||
|
||||
$this->set_ogTitle($object->get_name());
|
||||
$this->set_ogDescription($object->get_description());
|
||||
$this->set_ogUrl($object->get_page_url());
|
||||
$this->set_ogImage($object->get_cover());
|
||||
$this->set_ogTitle($object->getName());
|
||||
$this->set_ogDescription($object->getDescription());
|
||||
$this->set_ogUrl($object->getPageUrl());
|
||||
$this->set_ogImage($object->getCover());
|
||||
$this->set_ogType("music.song");
|
||||
$this->set_ogDurationSeconds($object->get_seconds());
|
||||
$this->set_ogDurationSeconds($object->getSeconds());
|
||||
$this->set_ogMusician($djList);
|
||||
$this->set_ogAudio($object->get_download_url());
|
||||
$this->set_ogAudio($object->getDownloadUrl());
|
||||
$this->set_ogKeywords($genreList);
|
||||
$this->set_ogCanonical($object->get_page_url());
|
||||
$this->set_ogCanonical($object->getPageUrl());
|
||||
$this->set_ogRobot("index, follow");
|
||||
$this->set_ogNoindex("");
|
||||
}
|
||||
|
|
456
classes/Mix.php
456
classes/Mix.php
|
@ -2,343 +2,277 @@
|
|||
|
||||
namespace DJMixHosting;
|
||||
|
||||
use DateTime;
|
||||
use Exception;
|
||||
|
||||
class Mix
|
||||
{
|
||||
|
||||
private $id = -1;
|
||||
private $enabled = false;
|
||||
private $name = "";
|
||||
private $slug = "";
|
||||
private $db = null;
|
||||
private $description = "";
|
||||
private $cover = "";
|
||||
private $url = "";
|
||||
private $seconds = 0;
|
||||
private $download_only = true;
|
||||
private $djs = [];
|
||||
private $genres = [];
|
||||
private $recorded;
|
||||
private $downloads = 0;
|
||||
private $created;
|
||||
private $updated;
|
||||
private $playcount = 0;
|
||||
private int $id = -1;
|
||||
private bool $enabled = false;
|
||||
private string $name = "";
|
||||
private string $slug = "";
|
||||
private $db;
|
||||
private string $description = "";
|
||||
private string $cover = "";
|
||||
private string $url = "";
|
||||
private int $seconds = 0;
|
||||
private bool $download_only = true;
|
||||
private array $djs = [];
|
||||
private array $genres = [];
|
||||
private ?string $recorded = null;
|
||||
private int $downloads = 0;
|
||||
private ?string $created = null;
|
||||
private ?string $updated = null;
|
||||
private int $playcount = 0;
|
||||
private $tracklist = [];
|
||||
private $loadDJs = true;
|
||||
private $related_mixes = [];
|
||||
private $duration = [];
|
||||
private $mixshow = [];
|
||||
private bool $loadDJs = true;
|
||||
private array $related_mixes = [];
|
||||
private array $duration = [];
|
||||
private array $mixshow = [];
|
||||
|
||||
|
||||
public function __construct($value, $db, $loadDJs = true)
|
||||
/**
|
||||
* Construct a Mix object using either an ID or slug.
|
||||
*
|
||||
* @param mixed $value The mix identifier (ID or slug).
|
||||
* @param mixed $db Database connection.
|
||||
* @param bool $loadDJs Whether to load DJ objects.
|
||||
*/
|
||||
public function __construct($value, $db, bool $loadDJs = true)
|
||||
{
|
||||
$this->db = $db;
|
||||
|
||||
// echo the type of value
|
||||
$this->loadDJs = $loadDJs;
|
||||
|
||||
if (ctype_digit((string)$value)) {
|
||||
$this->id = (int)$value;
|
||||
return $this->load_by_id();
|
||||
|
||||
$loaded = $this->loadById();
|
||||
} else {
|
||||
$this->slug = $value;
|
||||
return $this->load_by_slug();
|
||||
$loaded = $this->loadBySlug();
|
||||
}
|
||||
|
||||
if (!$loaded) {
|
||||
throw new Exception("Mix not found.");
|
||||
}
|
||||
}
|
||||
|
||||
private function load_by_id(): bool
|
||||
/**
|
||||
* Loads mix data by its ID.
|
||||
*/
|
||||
private function loadById(): bool
|
||||
{
|
||||
$mix = $this->get_mix_by_id();
|
||||
if ($mix && $mix['title'] != "") {
|
||||
return $this->build_mix($mix);
|
||||
} else {
|
||||
return false;
|
||||
$mix = $this->getMixById();
|
||||
if ($mix && !empty($mix['title'])) {
|
||||
return $this->buildMix($mix);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function get_mix_by_id()
|
||||
/**
|
||||
* Loads mix data by its slug.
|
||||
*/
|
||||
private function loadBySlug(): bool
|
||||
{
|
||||
// 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");
|
||||
$mix = $this->getMixBySlug();
|
||||
if ($mix && !empty($mix['title'])) {
|
||||
return $this->buildMix($mix);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve mix data by ID.
|
||||
*/
|
||||
private function getMixById(): ?array
|
||||
{
|
||||
// If the current user is admin, show all mixes;
|
||||
// Otherwise, only return mixes with pending = 0.
|
||||
$isAdmin = isset($_SESSION['user']) &&
|
||||
isset($_SESSION['user']['role']) &&
|
||||
$_SESSION['user']['role'] === 'admin';
|
||||
$query = $isAdmin
|
||||
? "SELECT * FROM mix WHERE id = ?"
|
||||
: "SELECT * FROM mix WHERE id = ? AND pending = 0";
|
||||
|
||||
$stmt = $this->db->prepare($query);
|
||||
$stmt->bind_param("i", $this->id);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
$mix = $result->fetch_assoc();
|
||||
$stmt->close();
|
||||
return $mix;
|
||||
return $mix ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $mix
|
||||
* @return true
|
||||
* Retrieve mix data by slug.
|
||||
*/
|
||||
private function build_mix($mix): bool
|
||||
private function getMixBySlug(): ?array
|
||||
{
|
||||
$this->id = $mix['id'];
|
||||
$this->name = $mix['title'];
|
||||
$this->slug = $mix['slug'];
|
||||
$this->description = $mix['description'];
|
||||
$this->cover = $this->legacyFix($mix['cover']);
|
||||
$this->url = $this->legacyFix($mix['url']);
|
||||
$this->seconds = $mix['seconds'];
|
||||
$this->duration = $this->configure_duration();
|
||||
$this->download_only = $mix['mediaplayer'];
|
||||
$this->recorded = $mix['recorded'];
|
||||
$this->created = $mix['created'];
|
||||
$this->updated = $mix['lastupdated'];
|
||||
$this->enabled = $mix['pending'];
|
||||
$isAdmin = isset($_SESSION['user']) &&
|
||||
isset($_SESSION['user']['role']) &&
|
||||
$_SESSION['user']['role'] === 'admin';
|
||||
$query = $isAdmin
|
||||
? "SELECT * FROM mix WHERE slug = ?"
|
||||
: "SELECT * FROM mix WHERE slug = ? AND pending = 0";
|
||||
|
||||
$stmt = $this->db->prepare($query);
|
||||
$stmt->bind_param("s", $this->slug);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
$mix = $result->fetch_assoc();
|
||||
$stmt->close();
|
||||
return $mix ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the mix object from database data.
|
||||
*/
|
||||
private function buildMix(array $mix): bool
|
||||
{
|
||||
$this->id = (int)$mix['id'];
|
||||
$this->name = $mix['title'] ?? "";
|
||||
$this->slug = $mix['slug'] ?? "";
|
||||
$this->description = $mix['description'] ?? "";
|
||||
$this->cover = $this->legacyFix($mix['cover'] ?? "");
|
||||
$this->url = $this->legacyFix($mix['url'] ?? "");
|
||||
$this->seconds = (int)($mix['seconds'] ?? 0);
|
||||
$this->duration = $this->configureDuration();
|
||||
$this->download_only = (bool)($mix['mediaplayer'] ?? true);
|
||||
$this->recorded = $mix['recorded'] ?? null;
|
||||
$this->created = $mix['created'] ?? null;
|
||||
$this->updated = $mix['lastupdated'] ?? null;
|
||||
$this->enabled = (bool)($mix['pending'] ?? false);
|
||||
|
||||
if ($this->loadDJs) {
|
||||
require_once 'DJ.php';
|
||||
$this->djs[] = new DJ($mix['dj1'], $this->db);
|
||||
if ($mix['dj2'] != null) {
|
||||
if (!empty($mix['dj2'])) {
|
||||
$this->djs[] = new DJ($mix['dj2'], $this->db);
|
||||
}
|
||||
if ($mix['dj3'] != null) {
|
||||
if (!empty($mix['dj3'])) {
|
||||
$this->djs[] = new DJ($mix['dj3'], $this->db);
|
||||
}
|
||||
|
||||
$this->djs = array_filter($this->djs);
|
||||
}
|
||||
|
||||
$this->load_mix_meta();
|
||||
$this->tracklist = $this->evaluate_tracklist();
|
||||
$this->loadMixMeta();
|
||||
$this->tracklist = $this->evaluateTracklist();
|
||||
return true;
|
||||
}
|
||||
|
||||
private function legacyFix(mixed $item)
|
||||
/**
|
||||
* Fix legacy URL paths.
|
||||
*/
|
||||
private function legacyFix(string $item): string
|
||||
{
|
||||
if (str_starts_with($item, "/djs/")) {
|
||||
return "https://cdn.utahsdjs.com" . substr($item, 4);
|
||||
} else {
|
||||
return $item;
|
||||
}
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function configure_duration(): array
|
||||
/**
|
||||
* Configure a formatted duration based on seconds.
|
||||
*/
|
||||
private function configureDuration(): array
|
||||
{
|
||||
$seconds = $this->seconds;
|
||||
$hours = floor($seconds / 3600);
|
||||
$minutes = floor(($seconds / 60) % 60);
|
||||
$seconds = $seconds % 60;
|
||||
|
||||
// for 't', we need to show it as 01:02:03
|
||||
if ($hours < 10) {
|
||||
$hours0 = "0" . $hours;
|
||||
} else {
|
||||
$hours0 = $hours;
|
||||
}
|
||||
if ($minutes < 10) {
|
||||
$minutes0 = "0" . $minutes;
|
||||
} else {
|
||||
$minutes0 = $minutes;
|
||||
}
|
||||
if ($seconds < 10) {
|
||||
$seconds0 = "0" . $seconds;
|
||||
} else {
|
||||
$seconds0 = $seconds;
|
||||
}
|
||||
|
||||
// if hours is 0, we don't need to show it
|
||||
$time = $hours > 0 ? $hours0 . ":" . $minutes0 . ":" . $seconds0 : $minutes0 . ":" . $seconds0;
|
||||
|
||||
return ['h' => $hours, 'm' => $minutes, 's' => $seconds, 't' => $time, 'S' => $this->seconds];
|
||||
$secs = $seconds % 60;
|
||||
$time = ($hours > 0)
|
||||
? sprintf("%02d:%02d:%02d", $hours, $minutes, $secs)
|
||||
: sprintf("%02d:%02d", $minutes, $secs);
|
||||
return [
|
||||
'h' => $hours,
|
||||
'm' => $minutes,
|
||||
's' => $secs,
|
||||
't' => $time,
|
||||
'S' => $this->seconds
|
||||
];
|
||||
}
|
||||
|
||||
private function load_mix_meta(): void
|
||||
/**
|
||||
* Load mix meta data.
|
||||
*/
|
||||
private function loadMixMeta(): void
|
||||
{
|
||||
$stmt = $this->db->prepare("SELECT attribute,value FROM mix_meta WHERE mix_id = ?");
|
||||
$stmt = $this->db->prepare("SELECT attribute, value FROM mix_meta WHERE mix_id = ?");
|
||||
$stmt->bind_param("i", $this->id);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
$meta = $result->fetch_all(MYSQLI_ASSOC);
|
||||
$stmt->close();
|
||||
|
||||
foreach ($meta as $key => $value) {
|
||||
if ($value['attribute'] == "genre") {
|
||||
$this->genres[] = $value['value'];
|
||||
unset($meta[$key]);
|
||||
foreach ($meta as $entry) {
|
||||
switch ($entry['attribute']) {
|
||||
case "genre":
|
||||
$this->genres[] = $entry['value'];
|
||||
break;
|
||||
case "related":
|
||||
$this->related_mixes[] = $entry['value'];
|
||||
break;
|
||||
case "playcount":
|
||||
$this->playcount = (int)$entry['value'];
|
||||
break;
|
||||
case "downloads":
|
||||
$this->downloads = (int)$entry['value'];
|
||||
break;
|
||||
case "tracklist":
|
||||
$this->tracklist = $entry['value'];
|
||||
break;
|
||||
case "mixshow":
|
||||
$this->mixshow[] = $entry['value'];
|
||||
break;
|
||||
default:
|
||||
// Handle additional meta attributes if needed.
|
||||
break;
|
||||
}
|
||||
if ($value['attribute'] == "related") {
|
||||
$this->related_mixes[] = $value['value'];
|
||||
unset($meta[$key]);
|
||||
}
|
||||
if ($value['attribute'] == "playcount") {
|
||||
$this->playcount = $value['value'];
|
||||
unset($meta[$key]);
|
||||
}
|
||||
if ($value['attribute'] == "downloads") {
|
||||
$this->downloads = $value['value'];
|
||||
unset($meta[$key]);
|
||||
}
|
||||
if ($value['attribute'] == "tracklist") {
|
||||
$this->tracklist = $value['value'];
|
||||
unset($meta[$key]);
|
||||
}
|
||||
if ($value['attribute'] == "mixshow") {
|
||||
$this->mixshow[] = $value['value'];
|
||||
unset($meta[$key]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function evaluate_tracklist()
|
||||
/**
|
||||
* Evaluate the tracklist data.
|
||||
*/
|
||||
private function evaluateTracklist()
|
||||
{
|
||||
if (empty($this->tracklist)) {
|
||||
return [];
|
||||
} else {
|
||||
// if the first item in the array is also an array, then return it
|
||||
if (is_array($this->tracklist)) {
|
||||
return $this->tracklist;
|
||||
} else {
|
||||
return explode("\n", (string)$this->tracklist);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function load_by_slug(): bool
|
||||
{
|
||||
$mix = $this->get_mix_by_slug();
|
||||
if ($mix) {
|
||||
if ($mix['title'] != "") {
|
||||
return $this->build_mix($mix);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
if (is_array($this->tracklist)) {
|
||||
return $this->tracklist;
|
||||
}
|
||||
return explode("\n", (string)$this->tracklist);
|
||||
}
|
||||
|
||||
// Getter methods for mix properties.
|
||||
|
||||
private function get_mix_by_slug()
|
||||
public function getId(): int { return $this->id; }
|
||||
public function getName(): string { return $this->name; }
|
||||
public function getSlug(): string { return $this->slug; }
|
||||
public function getDescription(): string { return $this->description; }
|
||||
public function getCover(): string { return $this->cover; }
|
||||
public function getUrl(): string { return $this->url; }
|
||||
public function getSeconds(): int { return $this->seconds; }
|
||||
public function isDownloadOnly(): bool { return $this->download_only; }
|
||||
public function getDJs(): array { return $this->djs; }
|
||||
public function getGenres(): array { return $this->genres; }
|
||||
public function getRecorded(): ?string { return $this->recorded; }
|
||||
public function getDownloads(): int { return $this->downloads; }
|
||||
public function getCreated(): ?string { return $this->created; }
|
||||
public function getUpdated(): ?string { return $this->updated; }
|
||||
public function getPlaycount(): int { return $this->playcount; }
|
||||
public function getTracklist(): array { return $this->evaluateTracklist(); }
|
||||
public function getRelatedMixes(): array { return $this->related_mixes; }
|
||||
public function getDuration(): array { return $this->duration; }
|
||||
public function getMixshow(): array { return $this->mixshow; }
|
||||
|
||||
public function getDownloadUrl(): string
|
||||
{
|
||||
// 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();
|
||||
$mix = $result->fetch_assoc();
|
||||
$stmt->close();
|
||||
return $mix;
|
||||
return "https://beta.utahsdjs.com/mix/{$this->slug}/download";
|
||||
}
|
||||
|
||||
public function get_recorded()
|
||||
public function getPageUrl(): string
|
||||
{
|
||||
return $this->recorded;
|
||||
return "https://beta.utahsdjs.com/mix/{$this->slug}";
|
||||
}
|
||||
|
||||
public function get_created()
|
||||
{
|
||||
return $this->created;
|
||||
}
|
||||
|
||||
public function get_updated()
|
||||
{
|
||||
return $this->updated;
|
||||
}
|
||||
|
||||
public function get_img()
|
||||
{
|
||||
return $this->cover;
|
||||
}
|
||||
|
||||
public function get_djs()
|
||||
{
|
||||
return $this->djs;
|
||||
}
|
||||
|
||||
public function get_description()
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
public function get_tracklist(): array
|
||||
{
|
||||
return $this->tracklist;
|
||||
}
|
||||
|
||||
public function get_genres()
|
||||
{
|
||||
return $this->genres;
|
||||
}
|
||||
|
||||
public function get_seconds(): int
|
||||
{
|
||||
return $this->seconds;
|
||||
}
|
||||
|
||||
public function is_download_only(): bool
|
||||
{
|
||||
return $this->download_only;
|
||||
}
|
||||
|
||||
public function get_url(): string
|
||||
{
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
public function get_cover()
|
||||
{
|
||||
return $this->cover;
|
||||
}
|
||||
|
||||
public function get_downloads(): int
|
||||
{
|
||||
return $this->downloads;
|
||||
}
|
||||
|
||||
public function get_plays(): int
|
||||
{
|
||||
return $this->playcount;
|
||||
}
|
||||
|
||||
public function get_id(): int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function get_name(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function get_slug(): string
|
||||
{
|
||||
return $this->slug;
|
||||
}
|
||||
|
||||
public function get_duration(): array
|
||||
{
|
||||
return $this->duration;
|
||||
}
|
||||
|
||||
public function get_mixshow(): array
|
||||
{
|
||||
return $this->mixshow;
|
||||
}
|
||||
|
||||
public function get_download_url()
|
||||
{
|
||||
return "https://beta.utahsdjs.com/mix/" . "$this->slug" . "/download";
|
||||
}
|
||||
|
||||
public function get_page_url()
|
||||
{
|
||||
return "https://beta.utahsdjs.com/mix/" . "$this->slug";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
39
classes/SessionManager.php
Normal file
39
classes/SessionManager.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace DJMixHosting;
|
||||
|
||||
class SessionManager {
|
||||
|
||||
/**
|
||||
* Ensure the session is started.
|
||||
*/
|
||||
public static function start() {
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set user data in session.
|
||||
*/
|
||||
public static function setUser(array $userData) {
|
||||
$_SESSION['user'] = $userData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current user data from session.
|
||||
*/
|
||||
public static function getUser() {
|
||||
return $_SESSION['user'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the session.
|
||||
*/
|
||||
public static function destroy() {
|
||||
if (session_status() !== PHP_SESSION_NONE) {
|
||||
session_unset();
|
||||
session_destroy();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,87 +4,125 @@ namespace DJMixHosting;
|
|||
|
||||
class Upload
|
||||
{
|
||||
private $file;
|
||||
private $file_name;
|
||||
private $file_size;
|
||||
private $file_tmp;
|
||||
private $file_type;
|
||||
private $file_ext;
|
||||
private $file_path;
|
||||
private $extensions = array("mp3", "zip");
|
||||
private $upload_dir = "uploads/";
|
||||
private $errors = array();
|
||||
private $ok = false;
|
||||
private $config;
|
||||
private $uuid = "";
|
||||
protected $file;
|
||||
protected $config;
|
||||
protected $errors = [];
|
||||
protected $uploadDir;
|
||||
protected $uuid;
|
||||
protected $fileExt;
|
||||
protected $localPath;
|
||||
|
||||
|
||||
public function __construct($file, $config)
|
||||
public function __construct(array $file, array $config)
|
||||
{
|
||||
$this->file = $file;
|
||||
$this->file_name = $file['name'];
|
||||
$this->file_size = $file['size'];
|
||||
$this->file_tmp = $file['tmp_name'];
|
||||
$this->file_type = $file['type'];
|
||||
$this->config = $config;
|
||||
$ext = explode('.', $file['name']);
|
||||
$this->file_ext = strtolower(end($ext));
|
||||
$this->uploadDir = rtrim($config['uploads']['tmp_path'], '/') . '/';
|
||||
$this->uuid = uniqid();
|
||||
|
||||
$this->fileExt = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
|
||||
}
|
||||
|
||||
private function check_file_size(): bool
|
||||
public function validate(): bool
|
||||
{
|
||||
if ($this->file_size > $this->config['uploads']['max_file_size']){
|
||||
$this->errors[] = "File size is too large";
|
||||
return false;
|
||||
if ($this->file['error'] !== UPLOAD_ERR_OK) {
|
||||
$this->errors[] = "File upload error.";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private function check_file_extension(): bool
|
||||
{
|
||||
if (!in_array($this->file_ext, $this->extensions)){
|
||||
$this->errors[] = "Invalid file extension";
|
||||
return false;
|
||||
if ($this->file['size'] > $this->config['uploads']['max_file_size']) {
|
||||
$this->errors[] = "File is too large.";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private function check_file_exists(): bool
|
||||
{
|
||||
if (file_exists($this->upload_dir . $this->file_name)){
|
||||
$this->errors[] = "File already exists";
|
||||
return false;
|
||||
if (!in_array($this->fileExt, $this->config['uploads']['allowed_file_types'])) {
|
||||
$this->errors[] = "File type not allowed.";
|
||||
}
|
||||
return true;
|
||||
if (!in_array($this->file['type'], $this->config['uploads']['allowed_mime_type'])) {
|
||||
$this->errors[] = "MIME type not allowed.";
|
||||
}
|
||||
return empty($this->errors);
|
||||
}
|
||||
|
||||
private function move_file(): bool
|
||||
public function getErrors(): array
|
||||
{
|
||||
if (move_uploaded_file($this->file_tmp, $this->upload_dir . $this->uuid)){
|
||||
$this->file_path = $this->upload_dir . $this->uuid;
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
public function moveFile(): bool
|
||||
{
|
||||
$destination = $this->uploadDir . $this->uuid . "." . $this->fileExt;
|
||||
if (move_uploaded_file($this->file['tmp_name'], $destination)) {
|
||||
$this->localPath = $destination;
|
||||
return true;
|
||||
}
|
||||
$this->errors[] = "Failed to move uploaded file.";
|
||||
return false;
|
||||
}
|
||||
|
||||
public function dump_all()
|
||||
public function process(): array
|
||||
{
|
||||
$array = array(
|
||||
"file" => $this->file,
|
||||
"file_name" => $this->file_name,
|
||||
"file_size" => $this->file_size,
|
||||
"file_tmp" => $this->file_tmp,
|
||||
"file_type" => $this->file_type,
|
||||
"file_ext" => $this->file_ext,
|
||||
"file_path" => $this->file_path,
|
||||
"extensions" => $this->extensions,
|
||||
"upload_dir" => $this->upload_dir,
|
||||
"errors" => $this->errors,
|
||||
"ok" => $this->ok,
|
||||
"uuid" => $this->uuid
|
||||
);
|
||||
// Assuming moveFile() has been called
|
||||
$uploadTask = [
|
||||
'original_name' => $this->file['name'],
|
||||
'local_path' => $this->localPath,
|
||||
'size' => $this->file['size'],
|
||||
'ext' => $this->fileExt,
|
||||
];
|
||||
|
||||
if ($this->fileExt === "mp3") {
|
||||
$escapedFile = escapeshellarg($this->localPath);
|
||||
$artist = trim(shell_exec("eyed3 --query='%a%' $escapedFile"));
|
||||
$title = trim(shell_exec("eyed3 --query='%t%' $escapedFile"));
|
||||
$duration = trim(shell_exec("mp3info -p \"%S\" $escapedFile"));
|
||||
|
||||
$uploadTask['file_type'] = 'mp3';
|
||||
$uploadTask['metadata'] = [
|
||||
'artist' => $artist,
|
||||
'title' => $title,
|
||||
'duration' => $duration,
|
||||
];
|
||||
$uploadTask['mediaplayer'] = 0;
|
||||
} elseif ($this->fileExt === "zip") {
|
||||
$zip = new \ZipArchive;
|
||||
if ($zip->open($this->localPath) === true) {
|
||||
$totalDuration = 0;
|
||||
$tracklist = [];
|
||||
for ($i = 0; $i < $zip->numFiles; $i++) {
|
||||
$entryName = $zip->getNameIndex($i);
|
||||
$entryExt = strtolower(pathinfo($entryName, PATHINFO_EXTENSION));
|
||||
if ($entryExt === "mp3") {
|
||||
$tempDir = $this->uploadDir . 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,
|
||||
];
|
||||
$uploadTask['mediaplayer'] = 1;
|
||||
} else {
|
||||
$this->errors[] = "Failed to open ZIP file.";
|
||||
}
|
||||
} else {
|
||||
$this->errors[] = "Unsupported file type.";
|
||||
}
|
||||
|
||||
return $uploadTask;
|
||||
}
|
||||
|
||||
public function uploadToCDN(CDN $cdn): string
|
||||
{
|
||||
// Create a remote path – e.g., under a temp folder with a unique name
|
||||
$remotePath = "temp/mixes/" . uniqid() . "_" . basename($this->localPath);
|
||||
$mimeType = ($this->fileExt === 'mp3') ? 'audio/mpeg' : 'application/zip';
|
||||
|
||||
$cdn->uploadFile($this->localPath, $remotePath, $mimeType);
|
||||
return $remotePath;
|
||||
}
|
||||
}
|
||||
|
|
295
classes/User.php
295
classes/User.php
|
@ -2,13 +2,19 @@
|
|||
|
||||
namespace DJMixHosting;
|
||||
|
||||
use DateTime;
|
||||
use Exception;
|
||||
use Random\RandomException;
|
||||
use Aws\Ses\SesClient;
|
||||
use Aws\Exception\AwsException;
|
||||
|
||||
Class User{
|
||||
class User {
|
||||
|
||||
private $db;
|
||||
private $id;
|
||||
private $username;
|
||||
private $firstName;
|
||||
private $lastName;
|
||||
private $email;
|
||||
private $location;
|
||||
private $bio;
|
||||
|
@ -16,42 +22,83 @@ Class User{
|
|||
private $updated;
|
||||
private $verified;
|
||||
private $role;
|
||||
private $img;
|
||||
|
||||
private $img = "";
|
||||
private $api_key;
|
||||
|
||||
public function __construct($db){
|
||||
public function __construct($db, $id = null) {
|
||||
$this->db = $db;
|
||||
if ($id) {
|
||||
$this->loadUserById($id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws RandomException
|
||||
* Load user data from the database by id.
|
||||
*/
|
||||
public function newUser($username, $password, $email){
|
||||
if ($this->check_existing_user($username, $email)){
|
||||
throw new RandomException("User already exists");
|
||||
private function loadUserById($id) {
|
||||
$stmt = $this->db->prepare("SELECT * FROM users WHERE id = ?");
|
||||
$stmt->bind_param("i", $id);
|
||||
$stmt->execute();
|
||||
$user_data = $stmt->get_result()->fetch_assoc();
|
||||
$stmt->close();
|
||||
|
||||
if ($user_data) {
|
||||
$this->id = $user_data['id'];
|
||||
$this->username = $user_data['username'];
|
||||
$this->firstName = $user_data['firstName'];
|
||||
$this->lastName = $user_data['lastName'];
|
||||
$this->email = $user_data['email'];
|
||||
$this->verified = $user_data['emailVerified'];
|
||||
$this->img = $user_data['img'];
|
||||
$this->api_key = $user_data['apiKey'];
|
||||
$this->created = $user_data['created'];
|
||||
$this->updated = $user_data['lastupdated'];
|
||||
$this->role = $user_data['isAdmin'] ? 'admin' : 'user';
|
||||
|
||||
// These fields are not in your table; assign defaults or remove them.
|
||||
$this->location = "";
|
||||
$this->bio = "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new user.
|
||||
*
|
||||
* @throws RandomException if the user already exists.
|
||||
*/
|
||||
public function newUser(string $username, string $password, string $email, string $firstName, string $lastName): int {
|
||||
if ($this->check_existing_user($username, $email)) {
|
||||
throw new \Random\RandomException("User already exists");
|
||||
}
|
||||
$this->username = $username;
|
||||
$this->email = $email;
|
||||
$password2 = password_hash($password, PASSWORD_DEFAULT);
|
||||
$this->password = $password2;
|
||||
$this->firstName = $firstName;
|
||||
$this->lastName = $lastName;
|
||||
$password_hashed = password_hash($password, PASSWORD_DEFAULT);
|
||||
|
||||
$this->location = "";
|
||||
$this->bio = "";
|
||||
$this->created = date('Y-m-d H:i:s');
|
||||
$this->updated = date('Y-m-d H:i:s');
|
||||
$this->verified = 0;
|
||||
$this->role = "user";
|
||||
$this->img = "";
|
||||
$this->api_key = bin2hex(random_bytes(32));
|
||||
// Set default values for optional fields.
|
||||
$this->location = "";
|
||||
$this->bio = "";
|
||||
$this->created = date('Y-m-d H:i:s');
|
||||
$this->updated = date('Y-m-d H:i:s');
|
||||
$this->verified = 0;
|
||||
$this->role = "user";
|
||||
$this->img = "";
|
||||
$this->api_key = bin2hex(random_bytes(32));
|
||||
|
||||
$stmt = $this->db->prepare("INSERT INTO users (username, password, email, img) VALUES (?, ?, ?, ?)");
|
||||
$stmt->bind_param("ssss", $this->username, $this->password, $this->email, $this->img);
|
||||
$stmt = $this->db->prepare("INSERT INTO users (username, password, email, firstName, lastName, img, emailVerified) VALUES (?, ?, ?, ?, ?, '', 0)");
|
||||
$stmt->bind_param("sssss", $this->username, $password_hashed, $this->email, $this->firstName, $this->lastName);
|
||||
$stmt->execute();
|
||||
$userId = $stmt->insert_id;
|
||||
$stmt->close();
|
||||
|
||||
$this->id = $userId;
|
||||
return $userId;
|
||||
}
|
||||
|
||||
private function check_existing_user($username, $email){
|
||||
|
||||
private function check_existing_user($username, $email) {
|
||||
$stmt = $this->db->prepare("SELECT * FROM users WHERE username = ? OR email = ?");
|
||||
$stmt->bind_param("ss", $username, $email);
|
||||
$stmt->execute();
|
||||
|
@ -61,8 +108,13 @@ Class User{
|
|||
return $user;
|
||||
}
|
||||
|
||||
public function login($email, $password)
|
||||
{
|
||||
/**
|
||||
* Login a user by email and password.
|
||||
*
|
||||
* Returns the user data array if successful. In case of failure,
|
||||
* a string error message is returned.
|
||||
*/
|
||||
public function login($email, $password) {
|
||||
// Retrieve user record by email
|
||||
$stmt = $this->db->prepare("SELECT * FROM users WHERE email = ?");
|
||||
$stmt->bind_param("s", $email);
|
||||
|
@ -78,10 +130,10 @@ Class User{
|
|||
$attempt_data = $stmt->get_result()->fetch_assoc();
|
||||
$stmt->close();
|
||||
|
||||
$current_time = new \DateTime();
|
||||
$current_time = new DateTime();
|
||||
|
||||
if ($attempt_data && !empty($attempt_data['lockout_until'])) {
|
||||
$lockout_until = new \DateTime($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.";
|
||||
}
|
||||
|
@ -95,15 +147,10 @@ Class User{
|
|||
|
||||
// Verify the password using password_verify
|
||||
if (password_verify($password, $user_data['password'])) {
|
||||
// Successful login – clear login attempts and set session variables
|
||||
// Successful login – clear login attempts.
|
||||
$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;
|
||||
// Return the user data for further session handling
|
||||
return $user_data;
|
||||
} else {
|
||||
$attempts = $this->updateFailedAttempt($email);
|
||||
return "Invalid email or password. Attempt $attempts of 3.";
|
||||
|
@ -115,8 +162,7 @@ Class User{
|
|||
* If attempts reach 3, set a lockout that doubles each time.
|
||||
* Returns the current number of attempts.
|
||||
*/
|
||||
private function updateFailedAttempt($email)
|
||||
{
|
||||
private function updateFailedAttempt($email) {
|
||||
// Check for an existing record
|
||||
$stmt = $this->db->prepare("SELECT * FROM login_attempts WHERE email = ?");
|
||||
$stmt->bind_param("s", $email);
|
||||
|
@ -124,7 +170,7 @@ Class User{
|
|||
$record = $stmt->get_result()->fetch_assoc();
|
||||
$stmt->close();
|
||||
|
||||
$current_time = new \DateTime();
|
||||
$current_time = new DateTime();
|
||||
|
||||
if ($record) {
|
||||
$attempts = $record['attempts'] + 1;
|
||||
|
@ -165,13 +211,186 @@ Class User{
|
|||
/**
|
||||
* Reset the login_attempts record for the given email.
|
||||
*/
|
||||
private function resetLoginAttempts($email)
|
||||
{
|
||||
private function resetLoginAttempts($email) {
|
||||
$stmt = $this->db->prepare("DELETE FROM login_attempts WHERE email = ?");
|
||||
$stmt->bind_param("s", $email);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user's email address.
|
||||
*
|
||||
* @param string $newEmail
|
||||
* @param array $config Configuration array for AWS SES and app settings.
|
||||
* @return string Success message.
|
||||
* @throws \Exception on validation or email-sending failure.
|
||||
*/
|
||||
public function updateEmail(string $newEmail, array $config): string {
|
||||
$newEmail = filter_var($newEmail, FILTER_VALIDATE_EMAIL);
|
||||
if (!$newEmail) {
|
||||
throw new \Exception("Invalid email format.");
|
||||
}
|
||||
|
||||
}
|
||||
// Update email and mark as unverified.
|
||||
$stmt = $this->db->prepare("UPDATE users SET email = ?, emailVerified = 0 WHERE id = ?");
|
||||
$stmt->bind_param("si", $newEmail, $this->id);
|
||||
$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.
|
||||
$stmt = $this->db->prepare("REPLACE INTO email_verifications (user_id, email, verification_code, expires_at) VALUES (?, ?, ?, ?)");
|
||||
$stmt->bind_param("isss", $this->id, $newEmail, $verification_code, $expires_at);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
// Use the new Email class to send the verification email.
|
||||
$emailObj = new Email($config);
|
||||
$emailObj->sendVerificationEmail($newEmail, $verification_code);
|
||||
|
||||
// Update the class properties.
|
||||
$this->email = $newEmail;
|
||||
$this->verified = 0;
|
||||
return "Email updated. A verification email has been sent to your new address.";
|
||||
}
|
||||
|
||||
public function updateName($firstName, $lastName) {
|
||||
// Update the user's name.
|
||||
$stmt = $this->db->prepare("UPDATE users SET firstName = ?, lastName = ? WHERE id = ?");
|
||||
$stmt->bind_param("ssi", $firstName, $lastName, $this->id);
|
||||
if (!$stmt->execute()) {
|
||||
$stmt->close();
|
||||
throw new Exception("Failed to update name. Please try again.");
|
||||
}
|
||||
$stmt->close();
|
||||
|
||||
// Optionally update class properties.
|
||||
$this->firstName = $firstName;
|
||||
$this->lastName = $lastName;
|
||||
return "Name updated successfully.";
|
||||
}
|
||||
|
||||
public function updatePassword($currentPassword, $newPassword, $confirmPassword) {
|
||||
// Retrieve the current password hash.
|
||||
$stmt = $this->db->prepare("SELECT password FROM users WHERE id = ?");
|
||||
$stmt->bind_param("i", $this->id);
|
||||
$stmt->execute();
|
||||
$userData = $stmt->get_result()->fetch_assoc();
|
||||
$stmt->close();
|
||||
|
||||
if (!$userData || !password_verify($currentPassword, $userData['password'])) {
|
||||
throw new Exception("Current password is incorrect.");
|
||||
}
|
||||
|
||||
if ($newPassword !== $confirmPassword) {
|
||||
throw new Exception("New password and confirmation do not match.");
|
||||
}
|
||||
|
||||
// Validate the new password.
|
||||
$pattern = '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_]).{8,32}$/';
|
||||
if (!preg_match($pattern, $newPassword)) {
|
||||
throw new Exception("New password must be 8-32 characters and include at least one uppercase letter, one lowercase letter, one number, and one symbol.");
|
||||
}
|
||||
|
||||
$hashed_new_password = password_hash($newPassword, PASSWORD_DEFAULT);
|
||||
$stmt = $this->db->prepare("UPDATE users SET password = ? WHERE id = ?");
|
||||
$stmt->bind_param("si", $hashed_new_password, $this->id);
|
||||
if (!$stmt->execute()) {
|
||||
$stmt->close();
|
||||
throw new Exception("Failed to update password. Please try again.");
|
||||
}
|
||||
$stmt->close();
|
||||
return "Password updated successfully.";
|
||||
}
|
||||
|
||||
public function updateUsername($newUsername) {
|
||||
// Validate username format.
|
||||
if (!preg_match('/^[a-zA-Z0-9_]{3,25}$/', $newUsername)) {
|
||||
throw new Exception("Invalid username format.");
|
||||
}
|
||||
|
||||
// Check if the new username already exists for another user.
|
||||
$stmt = $this->db->prepare("SELECT id FROM users WHERE username = ? AND id != ?");
|
||||
$stmt->bind_param("si", $newUsername, $this->id);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
if ($result->num_rows > 0) {
|
||||
$stmt->close();
|
||||
throw new Exception("Username already taken.");
|
||||
}
|
||||
$stmt->close();
|
||||
|
||||
// Update the username.
|
||||
$stmt = $this->db->prepare("UPDATE users SET username = ? WHERE id = ?");
|
||||
$stmt->bind_param("si", $newUsername, $this->id);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
$this->username = $newUsername;
|
||||
return "Username updated successfully.";
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the user's email using the provided verification code.
|
||||
*
|
||||
* @param string $verification_code The code submitted by the user.
|
||||
* @return string Success message.
|
||||
* @throws \Exception If the code is invalid or expired.
|
||||
*/
|
||||
public function verifyEmail(string $verification_code): string {
|
||||
// Look up the verification record for this user and code
|
||||
$stmt = $this->db->prepare("SELECT * FROM email_verifications WHERE user_id = ? AND verification_code = ?");
|
||||
$stmt->bind_param("is", $this->id, $verification_code);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
$record = $result->fetch_assoc();
|
||||
$stmt->close();
|
||||
|
||||
if (!$record) {
|
||||
throw new \Exception("Invalid verification code.");
|
||||
}
|
||||
|
||||
// Check if the verification code has expired
|
||||
$current_time = new \DateTime();
|
||||
$expires_at = new \DateTime($record['expires_at']);
|
||||
if ($current_time > $expires_at) {
|
||||
throw new \Exception("Verification code has expired. Please request a new one.");
|
||||
}
|
||||
|
||||
// Update the user's record to mark the email as verified
|
||||
$stmt = $this->db->prepare("UPDATE users SET emailVerified = 1 WHERE id = ?");
|
||||
$stmt->bind_param("i", $this->id);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
// Remove the verification record to clean up
|
||||
$stmt = $this->db->prepare("DELETE FROM email_verifications WHERE user_id = ?");
|
||||
$stmt->bind_param("i", $this->id);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
// Update the object property
|
||||
$this->verified = 1;
|
||||
|
||||
return "Email verified successfully.";
|
||||
}
|
||||
|
||||
// Getter methods
|
||||
public function getId() { return $this->id; }
|
||||
public function getUsername() { return $this->username; }
|
||||
public function getFirstName() { return $this->firstName; }
|
||||
public function getLastName() { return $this->lastName; }
|
||||
public function getEmail() { return $this->email; }
|
||||
public function getLocation() { return $this->location; }
|
||||
public function getBio() { return $this->bio; }
|
||||
public function getCreated() { return $this->created; }
|
||||
public function getUpdated() { return $this->updated; }
|
||||
public function getVerified() { return $this->verified; }
|
||||
public function getRole() { return $this->role; }
|
||||
public function getImg() { return $this->img; }
|
||||
public function getApiKey() { return $this->api_key; }
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue