Major additions: - Web-based setup wizard (setup.php, setup_wizard.php, setup-wizard.js) - Production Docker configuration (docker-compose.prod.yml, .env.production) - Database initialization SQL files (deploy/init_settings.sql) - Template builder system with drag-and-drop UI - Advanced features (OAuth, CDN, enhanced analytics, monetization) - Comprehensive documentation (deployment guides, quick start, feature docs) - Design system with accessibility and responsive layout - Deployment automation scripts (deploy.ps1, generate-secrets.ps1) Setup wizard allows customization of: - Platform name and branding - Domain configuration - Membership tiers and pricing - Admin credentials - Feature toggles Database includes 270+ tables for complete video streaming platform with advanced features for analytics, moderation, template building, and monetization. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
298 lines
9.9 KiB
PHP
298 lines
9.9 KiB
PHP
<?php
|
|
/**
|
|
* EasyStream Email Queue System
|
|
* SendGrid/AWS SES integration with queue processing
|
|
* Version: 1.0
|
|
*/
|
|
|
|
defined('_ISVALID') or header('Location: /error');
|
|
|
|
class VEmailQueue {
|
|
private static $db;
|
|
private static $sendgrid_api_key = null;
|
|
private static $from_email = 'noreply@easystream.com';
|
|
private static $from_name = 'EasyStream';
|
|
|
|
public static function init() {
|
|
self::$db = VDatabase::getInstance();
|
|
|
|
// Load configuration
|
|
$config = VGenerate::getConfig('email_config');
|
|
if ($config) {
|
|
self::$sendgrid_api_key = $config['sendgrid_api_key'] ?? null;
|
|
self::$from_email = $config['from_email'] ?? self::$from_email;
|
|
self::$from_name = $config['from_name'] ?? self::$from_name;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Queue email for sending
|
|
* @param int $usr_id User ID (optional)
|
|
* @param string $to_email Recipient email
|
|
* @param string $subject Email subject
|
|
* @param string $body_html HTML body
|
|
* @param array $options Additional options
|
|
* @return int Email ID
|
|
*/
|
|
public static function queueEmail($usr_id, $to_email, $subject, $body_html, $options = []) {
|
|
self::init();
|
|
|
|
$usr_id_val = $usr_id ? (int)$usr_id : 'NULL';
|
|
$to_email_safe = VDatabase::escape($to_email);
|
|
$to_name_safe = VDatabase::escape($options['to_name'] ?? '');
|
|
$subject_safe = VDatabase::escape($subject);
|
|
$body_html_safe = VDatabase::escape($body_html);
|
|
$body_text_safe = VDatabase::escape($options['body_text'] ?? strip_tags($body_html));
|
|
$template_safe = VDatabase::escape($options['template_name'] ?? '');
|
|
$template_data = VDatabase::escape(json_encode($options['template_data'] ?? []));
|
|
$priority = (int)($options['priority'] ?? 5);
|
|
$send_at = isset($options['send_at']) ? "'" . VDatabase::escape($options['send_at']) . "'" : 'NULL';
|
|
|
|
$sql = "INSERT INTO db_email_queue
|
|
(usr_id, to_email, to_name, subject, body_html, body_text, template_name, template_data, priority, send_at, status, created_at)
|
|
VALUES ($usr_id_val, '$to_email_safe', '$to_name_safe', '$subject_safe', '$body_html_safe', '$body_text_safe', '$template_safe', '$template_data', $priority, $send_at, 'pending', NOW())";
|
|
|
|
if (self::$db->execute($sql)) {
|
|
return self::$db->insert_id();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Process email queue
|
|
* @param int $limit Number of emails to process
|
|
* @return array Results
|
|
*/
|
|
public static function processQueue($limit = 50) {
|
|
self::init();
|
|
|
|
$limit = (int)$limit;
|
|
|
|
// Get pending emails
|
|
$sql = "SELECT * FROM db_email_queue
|
|
WHERE status = 'pending'
|
|
AND (send_at IS NULL OR send_at <= NOW())
|
|
ORDER BY priority ASC, created_at ASC
|
|
LIMIT $limit";
|
|
|
|
$result = self::$db->execute($sql);
|
|
|
|
if (!$result) {
|
|
return ['sent' => 0, 'failed' => 0];
|
|
}
|
|
|
|
$sent = 0;
|
|
$failed = 0;
|
|
|
|
while ($row = $result->FetchRow()) {
|
|
$email_id = (int)$row['email_id'];
|
|
|
|
// Mark as sending
|
|
self::$db->execute("UPDATE db_email_queue SET status = 'sending' WHERE email_id = $email_id");
|
|
|
|
// Send email
|
|
$success = self::sendEmail($row);
|
|
|
|
if ($success) {
|
|
self::$db->execute("UPDATE db_email_queue SET status = 'sent', sent_at = NOW() WHERE email_id = $email_id");
|
|
self::logEmail($email_id, $row, 'sent');
|
|
$sent++;
|
|
} else {
|
|
$attempts = (int)$row['attempts'] + 1;
|
|
$max_attempts = 3;
|
|
|
|
if ($attempts >= $max_attempts) {
|
|
self::$db->execute("UPDATE db_email_queue SET status = 'failed', attempts = $attempts WHERE email_id = $email_id");
|
|
self::logEmail($email_id, $row, 'failed');
|
|
$failed++;
|
|
} else {
|
|
// Retry later
|
|
self::$db->execute("UPDATE db_email_queue SET status = 'pending', attempts = $attempts WHERE email_id = $email_id");
|
|
}
|
|
}
|
|
}
|
|
|
|
return ['sent' => $sent, 'failed' => $failed];
|
|
}
|
|
|
|
/**
|
|
* Send individual email
|
|
* @param array $email_data Email data
|
|
* @return bool Success
|
|
*/
|
|
private static function sendEmail($email_data) {
|
|
if (self::$sendgrid_api_key) {
|
|
return self::sendViaSendGrid($email_data);
|
|
} else {
|
|
return self::sendViaPHPMailer($email_data);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send via SendGrid
|
|
*/
|
|
private static function sendViaSendGrid($email_data) {
|
|
$url = 'https://api.sendgrid.com/v3/mail/send';
|
|
|
|
$data = [
|
|
'personalizations' => [[
|
|
'to' => [['email' => $email_data['to_email'], 'name' => $email_data['to_name']]]
|
|
]],
|
|
'from' => ['email' => self::$from_email, 'name' => self::$from_name],
|
|
'subject' => $email_data['subject'],
|
|
'content' => [
|
|
['type' => 'text/html', 'value' => $email_data['body_html']],
|
|
['type' => 'text/plain', 'value' => $email_data['body_text']]
|
|
]
|
|
];
|
|
|
|
$json = json_encode($data);
|
|
|
|
$ch = curl_init($url);
|
|
curl_setopt($ch, CURLOPT_POST, true);
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
'Authorization: Bearer ' . self::$sendgrid_api_key,
|
|
'Content-Type: application/json'
|
|
]);
|
|
|
|
$result = curl_exec($ch);
|
|
$status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
curl_close($ch);
|
|
|
|
return $status_code == 202 || $status_code == 200;
|
|
}
|
|
|
|
/**
|
|
* Send via PHP mail (fallback)
|
|
*/
|
|
private static function sendViaPHPMailer($email_data) {
|
|
$to = $email_data['to_email'];
|
|
$subject = $email_data['subject'];
|
|
$message = $email_data['body_html'];
|
|
|
|
$headers = "MIME-Version: 1.0\r\n";
|
|
$headers .= "Content-type: text/html; charset=UTF-8\r\n";
|
|
$headers .= "From: " . self::$from_name . " <" . self::$from_email . ">\r\n";
|
|
|
|
return mail($to, $subject, $message, $headers);
|
|
}
|
|
|
|
/**
|
|
* Log email event
|
|
*/
|
|
private static function logEmail($email_id, $email_data, $status) {
|
|
$usr_id = $email_data['usr_id'] ?? 'NULL';
|
|
$to_email = VDatabase::escape($email_data['to_email']);
|
|
$subject = VDatabase::escape($email_data['subject']);
|
|
$status_safe = VDatabase::escape($status);
|
|
|
|
$sql = "INSERT INTO db_email_logs
|
|
(email_id, usr_id, to_email, subject, status, created_at)
|
|
VALUES ($email_id, $usr_id, '$to_email', '$subject', '$status_safe', NOW())";
|
|
|
|
self::$db->execute($sql);
|
|
}
|
|
|
|
/**
|
|
* Get user email preferences
|
|
* @param int $usr_id User ID
|
|
* @return array Preferences
|
|
*/
|
|
public static function getPreferences($usr_id) {
|
|
self::init();
|
|
|
|
$usr_id = (int)$usr_id;
|
|
|
|
$sql = "SELECT * FROM db_email_preferences WHERE usr_id = $usr_id";
|
|
$result = self::$db->execute($sql);
|
|
|
|
if ($result && $result->RecordCount() > 0) {
|
|
return $result->FetchRow();
|
|
}
|
|
|
|
// Return defaults
|
|
return [
|
|
'digest_frequency' => 'weekly',
|
|
'notify_comments' => 1,
|
|
'notify_replies' => 1,
|
|
'notify_likes' => 1,
|
|
'notify_subscribers' => 1,
|
|
'notify_uploads' => 1,
|
|
'notify_live_streams' => 1,
|
|
'notify_mentions' => 1,
|
|
'notify_milestones' => 1,
|
|
'marketing_emails' => 1
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Update email preferences
|
|
* @param int $usr_id User ID
|
|
* @param array $preferences Preferences
|
|
* @return bool Success
|
|
*/
|
|
public static function updatePreferences($usr_id, $preferences) {
|
|
self::init();
|
|
|
|
$usr_id = (int)$usr_id;
|
|
|
|
// Build SET clause
|
|
$sets = [];
|
|
foreach ($preferences as $key => $value) {
|
|
$key_safe = VDatabase::escape($key);
|
|
$value_safe = VDatabase::escape($value);
|
|
$sets[] = "$key_safe = '$value_safe'";
|
|
}
|
|
$setClause = implode(', ', $sets);
|
|
|
|
$sql = "INSERT INTO db_email_preferences (usr_id, $setClause, updated_at)
|
|
VALUES ($usr_id, NOW())
|
|
ON DUPLICATE KEY UPDATE $setClause, updated_at = NOW()";
|
|
|
|
return self::$db->execute($sql);
|
|
}
|
|
|
|
/**
|
|
* Send templated email
|
|
* @param string $template_name Template name
|
|
* @param int $usr_id User ID
|
|
* @param string $to_email Email address
|
|
* @param array $variables Template variables
|
|
* @return int Email ID
|
|
*/
|
|
public static function sendTemplate($template_name, $usr_id, $to_email, $variables = []) {
|
|
self::init();
|
|
|
|
// Get template
|
|
$template_safe = VDatabase::escape($template_name);
|
|
$sql = "SELECT * FROM db_email_templates WHERE name = '$template_safe' AND is_active = 1";
|
|
$result = self::$db->execute($sql);
|
|
|
|
if (!$result || $result->RecordCount() == 0) {
|
|
return false;
|
|
}
|
|
|
|
$template = $result->FetchRow();
|
|
|
|
// Replace variables
|
|
$subject = $template['subject'];
|
|
$body_html = $template['body_html'];
|
|
$body_text = $template['body_text'];
|
|
|
|
foreach ($variables as $key => $value) {
|
|
$subject = str_replace('{' . $key . '}', $value, $subject);
|
|
$body_html = str_replace('{' . $key . '}', $value, $body_html);
|
|
$body_text = str_replace('{' . $key . '}', $value, $body_text);
|
|
}
|
|
|
|
return self::queueEmail($usr_id, $to_email, $subject, $body_html, [
|
|
'body_text' => $body_text,
|
|
'template_name' => $template_name,
|
|
'template_data' => $variables
|
|
]);
|
|
}
|
|
}
|