Initial commit

This commit is contained in:
2026-05-22 12:52:45 +00:00
commit 0f11edc62e
34 changed files with 3095 additions and 0 deletions
+52
View File
@@ -0,0 +1,52 @@
<?php
/**
* Database Connection Class
* Uses PDO for secure MySQL connections
*/
class Database {
private static $instance = null;
private $conn;
private function __construct() {
try {
$dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=" . DB_CHARSET;
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
$this->conn = new PDO($dsn, DB_USER, DB_PASS, $options);
} catch (PDOException $e) {
$this->handleError($e->getMessage());
}
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
public function getConnection() {
return $this->conn;
}
private function handleError($message) {
if (DEBUG_MODE) {
die(json_encode(['error' => 'Database Error: ' . $message]));
} else {
die(json_encode(['error' => 'Database connection failed']));
}
}
// Prevent cloning
private function __clone() {}
// Prevent unserialization
public function __wakeup() {
throw new Exception("Cannot unserialize singleton");
}
}
+87
View File
@@ -0,0 +1,87 @@
<?php
/**
* Helper Functions
*/
/**
* Set CORS headers
*/
function setCorsHeaders() {
$origin = isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : '';
if ($origin && (ALLOWED_ORIGINS === '*' || strpos(ALLOWED_ORIGINS, $origin) !== false)) {
header("Access-Control-Allow-Origin: $origin");
} else {
header("Access-Control-Allow-Origin: " . ALLOWED_ORIGINS);
}
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
header("Access-Control-Allow-Credentials: true");
// Handle preflight requests
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit;
}
}
/**
* Send JSON response
*/
function jsonResponse($data, $statusCode = 200) {
http_response_code($statusCode);
header('Content-Type: application/json');
echo json_encode($data);
exit;
}
/**
* Get JSON input
*/
function getJsonInput() {
$input = file_get_contents('php://input');
return json_decode($input, true);
}
/**
* Validate email
*/
function isValidEmail($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
/**
* Generate UUID v4
*/
function generateUuid() {
return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0x0fff) | 0x4000,
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
);
}
/**
* Sanitize string
*/
function sanitizeString($string) {
return htmlspecialchars(strip_tags(trim($string)), ENT_QUOTES, 'UTF-8');
}
/**
* Validate required fields
*/
function validateRequired($data, $requiredFields) {
$errors = [];
foreach ($requiredFields as $field) {
if (!isset($data[$field]) || empty(trim($data[$field]))) {
$errors[] = "$field is required";
}
}
return $errors;
}
+117
View File
@@ -0,0 +1,117 @@
<?php
/**
* JWT Authentication Functions
* Simple JWT implementation for PHP
*/
class JWT {
/**
* Create a JWT token
*/
public static function encode($payload, $secret) {
$header = json_encode(['typ' => 'JWT', 'alg' => 'HS256']);
$payload = json_encode($payload);
$base64UrlHeader = self::base64UrlEncode($header);
$base64UrlPayload = self::base64UrlEncode($payload);
$signature = hash_hmac('sha256', $base64UrlHeader . "." . $base64UrlPayload, $secret, true);
$base64UrlSignature = self::base64UrlEncode($signature);
return $base64UrlHeader . "." . $base64UrlPayload . "." . $base64UrlSignature;
}
/**
* Decode and verify a JWT token
*/
public static function decode($jwt, $secret) {
$parts = explode('.', $jwt);
if (count($parts) !== 3) {
return false;
}
list($base64UrlHeader, $base64UrlPayload, $base64UrlSignature) = $parts;
$signature = self::base64UrlDecode($base64UrlSignature);
$expectedSignature = hash_hmac('sha256', $base64UrlHeader . "." . $base64UrlPayload, $secret, true);
if (!hash_equals($signature, $expectedSignature)) {
return false;
}
$payload = json_decode(self::base64UrlDecode($base64UrlPayload), true);
// Check expiration
if (isset($payload['exp']) && $payload['exp'] < time()) {
return false;
}
return $payload;
}
/**
* Create authentication token
*/
public static function createToken($email) {
$payload = [
'sub' => $email,
'iat' => time(),
'exp' => time() + JWT_EXPIRY
];
return self::encode($payload, JWT_SECRET_KEY);
}
/**
* Verify token from Authorization header
*/
public static function verifyToken() {
$headers = getallheaders();
if (!isset($headers['Authorization'])) {
return false;
}
$authHeader = $headers['Authorization'];
if (!preg_match('/Bearer\s+(.*)$/i', $authHeader, $matches)) {
return false;
}
$token = $matches[1];
$payload = self::decode($token, JWT_SECRET_KEY);
return $payload;
}
/**
* Base64 URL encode
*/
private static function base64UrlEncode($data) {
return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($data));
}
/**
* Base64 URL decode
*/
private static function base64UrlDecode($data) {
return base64_decode(str_replace(['-', '_'], ['+', '/'], $data));
}
}
/**
* Require authentication middleware
*/
function requireAuth() {
$payload = JWT::verifyToken();
if (!$payload) {
http_response_code(401);
echo json_encode(['error' => 'Unauthorized']);
exit;
}
return $payload;
}
+101
View File
@@ -0,0 +1,101 @@
<?php
/**
* Epic Travel Expeditions — SendGrid Mailer
*/
function sendgridSend(string $toEmail, string $toName, string $subject, string $htmlBody, string $textBody = ''): bool {
$apiKey = defined('SENDGRID_API_KEY') ? SENDGRID_API_KEY : '';
if (!$apiKey || strpos($apiKey, 'YOUR_KEY') !== false) {
error_log('[EpicTravel mailer] SENDGRID_API_KEY not configured');
return false;
}
$payload = json_encode([
'personalizations' => [['to' => [['email' => $toEmail, 'name' => $toName]]]],
'from' => [
'email' => defined('MAIL_FROM') ? MAIL_FROM : 'noreply@epictravelexpeditions.com',
'name' => defined('MAIL_FROM_NAME') ? MAIL_FROM_NAME : 'Epic Travel Expeditions',
],
'subject' => $subject,
'content' => array_values(array_filter([
$textBody ? ['type' => 'text/plain', 'value' => $textBody] : null,
['type' => 'text/html', 'value' => $htmlBody],
])),
]);
$ch = curl_init('https://api.sendgrid.com/v3/mail/send');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_TIMEOUT => 20,
CURLOPT_SSL_VERIFYPEER => false,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 202) return true;
error_log('[EpicTravel mailer] SendGrid HTTP ' . $httpCode . ' — ' . $response);
return false;
}
function sendContactAlert(string $name, string $email, string $message): bool {
$adminEmail = defined('ADMIN_EMAIL') ? ADMIN_EMAIL : 'admin@epictravelexpeditions.com';
$subject = "New Contact Form Submission from {$name}";
$html = '
<div style="max-width:600px;margin:0 auto;font-family:Arial,sans-serif;">
<div style="background:#1a3a6b;padding:24px;text-align:center;">
<h1 style="color:#fff;margin:0;font-size:22px;">Epic Travel Expeditions</h1>
<p style="color:rgba(255,255,255,.7);margin:4px 0 0;font-size:14px;">New Contact Form Message</p>
</div>
<div style="padding:28px;background:#fff;border:1px solid #e5e7eb;">
<table style="width:100%;border-collapse:collapse;">
<tr><td style="padding:8px 0;color:#6b7280;font-size:13px;width:80px;">Name</td>
<td style="padding:8px 0;font-weight:600;">' . htmlspecialchars($name) . '</td></tr>
<tr><td style="padding:8px 0;color:#6b7280;font-size:13px;">Email</td>
<td style="padding:8px 0;"><a href="mailto:' . htmlspecialchars($email) . '" style="color:#1a3a6b;">' . htmlspecialchars($email) . '</a></td></tr>
</table>
<div style="margin-top:20px;padding:16px;background:#f9fafb;border-radius:8px;border-left:4px solid #1a3a6b;">
<p style="margin:0;font-size:14px;color:#374151;line-height:1.6;">' . nl2br(htmlspecialchars($message)) . '</p>
</div>
<p style="margin-top:20px;font-size:13px;color:#9ca3af;">Submitted ' . date('F j, Y \a\t g:i A T') . '</p>
</div>
<div style="background:#f3f4f6;padding:16px;text-align:center;">
<p style="margin:0;font-size:12px;color:#9ca3af;">&copy; ' . date('Y') . ' Epic Travel Expeditions</p>
</div>
</div>';
return sendgridSend($adminEmail, 'Epic Travel Admin', $subject, $html,
"New contact from {$name} ({$email}):\n\n{$message}");
}
function sendContactConfirmation(string $toEmail, string $toName): bool {
$subject = "We received your message — Epic Travel Expeditions";
$html = '
<div style="max-width:600px;margin:0 auto;font-family:Arial,sans-serif;">
<div style="background:#1a3a6b;padding:24px;text-align:center;">
<h1 style="color:#fff;margin:0;font-size:22px;">Epic Travel Expeditions</h1>
</div>
<div style="padding:32px;background:#fff;">
<h2 style="margin-top:0;color:#1a3a6b;">Thanks for reaching out, ' . htmlspecialchars($toName) . '!</h2>
<p style="color:#374151;line-height:1.6;">We received your message and our team will get back to you within 12 business days.</p>
<p style="color:#374151;line-height:1.6;">In the meantime, feel free to browse our destinations and current travel specials:</p>
<div style="text-align:center;margin:28px 0;">
<a href="https://epictravelexpeditions.com" style="display:inline-block;background:#1a3a6b;color:#fff;padding:14px 28px;border-radius:8px;text-decoration:none;font-weight:bold;">Explore Destinations</a>
</div>
<p style="color:#374151;line-height:1.6;">Adventure awaits,<br><strong>The Epic Travel Team</strong></p>
</div>
<div style="background:#f3f4f6;padding:16px;text-align:center;">
<p style="margin:0;font-size:12px;color:#9ca3af;">&copy; ' . date('Y') . ' Epic Travel Expeditions</p>
</div>
</div>';
return sendgridSend($toEmail, $toName, $subject, $html,
"Hi {$toName},\n\nThanks for contacting Epic Travel Expeditions! We'll get back to you within 1-2 business days.\n\nAdventure awaits,\nThe Epic Travel Team");
}