feat: Add comprehensive documentation suite and reorganize project structure

- Created complete documentation in docs/ directory
- Added PROJECT_OVERVIEW.md with feature highlights and getting started guide
- Added ARCHITECTURE.md with system design and technical details
- Added SECURITY.md with comprehensive security implementation guide
- Added DEVELOPMENT.md with development workflows and best practices
- Added DEPLOYMENT.md with production deployment instructions
- Added API.md with complete REST API documentation
- Added CONTRIBUTING.md with contribution guidelines
- Added CHANGELOG.md with version history and migration notes
- Reorganized all documentation files into docs/ directory for better organization
- Updated README.md with proper documentation links and quick navigation
- Enhanced project structure with professional documentation standards
This commit is contained in:
SamiAhmed7777
2025-10-21 00:39:45 -07:00
commit 0b7e2d0a5b
6080 changed files with 1332936 additions and 0 deletions

117
f_jobs/BaseJob.php Normal file
View File

@@ -0,0 +1,117 @@
<?php
/*******************************************************************************************************************
| Base Job Class
| All queue jobs should extend this class
|*******************************************************************************************************************/
abstract class BaseJob
{
protected $logger;
protected $startTime;
public function __construct()
{
$this->logger = VLogger::getInstance();
$this->startTime = microtime(true);
}
/**
* Handle the job - must be implemented by child classes
* @param array $data Job data
* @return mixed Job result
*/
abstract public function handle($data);
/**
* Log job progress
* @param string $message Progress message
* @param array $context Additional context
*/
protected function logProgress($message, $context = [])
{
$context['job_class'] = get_class($this);
$context['elapsed_time'] = microtime(true) - $this->startTime;
$this->logger->info($message, $context);
}
/**
* Log job error
* @param string $message Error message
* @param array $context Additional context
*/
protected function logError($message, $context = [])
{
$context['job_class'] = get_class($this);
$context['elapsed_time'] = microtime(true) - $this->startTime;
$this->logger->error($message, $context);
}
/**
* Validate required data fields
* @param array $data Job data
* @param array $required Required field names
* @throws Exception If required fields are missing
*/
protected function validateData($data, $required = [])
{
foreach ($required as $field) {
if (!isset($data[$field])) {
throw new Exception("Required field '{$field}' is missing from job data");
}
}
}
/**
* Get database connection
* @return object Database connection
*/
protected function getDatabase()
{
global $class_database;
return $class_database;
}
/**
* Get Redis connection
* @return VRedis Redis instance
*/
protected function getRedis()
{
return VRedis::getInstance();
}
/**
* Send notification (can be overridden)
* @param string $message Notification message
* @param array $data Notification data
*/
protected function sendNotification($message, $data = [])
{
// Default implementation - log the notification
$this->logger->info('Job notification', [
'message' => $message,
'data' => $data,
'job_class' => get_class($this)
]);
}
/**
* Update job progress (for long-running jobs)
* @param int $current Current progress
* @param int $total Total items
* @param string $message Progress message
*/
protected function updateProgress($current, $total, $message = '')
{
$percentage = $total > 0 ? round(($current / $total) * 100, 2) : 0;
$this->logProgress("Job progress: {$percentage}%", [
'current' => $current,
'total' => $total,
'percentage' => $percentage,
'message' => $message
]);
}
}

450
f_jobs/CleanupJob.php Normal file
View File

@@ -0,0 +1,450 @@
<?php
/*******************************************************************************************************************
| System Cleanup Job
| Handles automated system maintenance and cleanup tasks
|*******************************************************************************************************************/
class CleanupJob extends BaseJob
{
private $queueManager;
public function __construct()
{
parent::__construct();
$this->queueManager = new VQueueManager();
}
/**
* Handle cleanup job
* @param array $data Cleanup configuration data
* @return array Cleanup result
*/
public function handle($data)
{
$cleanupType = $data['type'] ?? 'full';
$this->logProgress('Starting system cleanup', [
'type' => $cleanupType,
'data' => $data
]);
$results = [];
try {
switch ($cleanupType) {
case 'full':
$results = $this->performFullCleanup($data);
break;
case 'logs':
$results['logs'] = $this->cleanupLogs($data);
break;
case 'temp_files':
$results['temp_files'] = $this->cleanupTempFiles($data);
break;
case 'old_sessions':
$results['sessions'] = $this->cleanupOldSessions($data);
break;
case 'failed_uploads':
$results['failed_uploads'] = $this->cleanupFailedUploads($data);
break;
case 'analytics':
$results['analytics'] = $this->cleanupOldAnalytics($data);
break;
default:
throw new Exception("Unknown cleanup type: {$cleanupType}");
}
$this->logProgress('System cleanup completed', [
'type' => $cleanupType,
'results' => $results
]);
return [
'success' => true,
'type' => $cleanupType,
'results' => $results,
'message' => 'Cleanup completed successfully'
];
} catch (Exception $e) {
$this->logError('System cleanup failed', [
'type' => $cleanupType,
'error' => $e->getMessage()
]);
throw $e;
}
}
/**
* Perform full system cleanup
* @param array $data Configuration data
* @return array Cleanup results
*/
private function performFullCleanup($data)
{
$results = [];
$this->updateProgress(1, 8, 'Cleaning up logs');
$results['logs'] = $this->cleanupLogs($data);
$this->updateProgress(2, 8, 'Cleaning up temporary files');
$results['temp_files'] = $this->cleanupTempFiles($data);
$this->updateProgress(3, 8, 'Cleaning up old sessions');
$results['sessions'] = $this->cleanupOldSessions($data);
$this->updateProgress(4, 8, 'Cleaning up failed uploads');
$results['failed_uploads'] = $this->cleanupFailedUploads($data);
$this->updateProgress(5, 8, 'Cleaning up old analytics');
$results['analytics'] = $this->cleanupOldAnalytics($data);
$this->updateProgress(6, 8, 'Cleaning up queue jobs');
$results['queue_jobs'] = $this->queueManager->cleanupOldJobs(
$data['queue_retention_hours'] ?? 24
);
$this->updateProgress(7, 8, 'Cleaning up orphaned files');
$results['orphaned_files'] = $this->cleanupOrphanedFiles($data);
$this->updateProgress(8, 8, 'Optimizing database');
$results['database'] = $this->optimizeDatabase($data);
return $results;
}
/**
* Cleanup old log files
* @param array $data Configuration data
* @return array Cleanup result
*/
private function cleanupLogs($data)
{
$logDir = _FPATH . 'f_data/logs/';
$retentionDays = $data['log_retention_days'] ?? 30;
$cutoffTime = time() - ($retentionDays * 24 * 3600);
$deletedFiles = 0;
$deletedSize = 0;
if (is_dir($logDir)) {
$files = glob($logDir . '*.log');
foreach ($files as $file) {
if (filemtime($file) < $cutoffTime) {
$size = filesize($file);
if (unlink($file)) {
$deletedFiles++;
$deletedSize += $size;
}
}
}
}
return [
'deleted_files' => $deletedFiles,
'deleted_size' => $deletedSize,
'retention_days' => $retentionDays
];
}
/**
* Cleanup temporary files
* @param array $data Configuration data
* @return array Cleanup result
*/
private function cleanupTempFiles($data)
{
$tempDir = _FPATH . 'f_data/temp/';
$maxAge = $data['temp_file_max_age_hours'] ?? 24;
$cutoffTime = time() - ($maxAge * 3600);
$deletedFiles = 0;
$deletedSize = 0;
if (is_dir($tempDir)) {
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($tempDir, RecursiveDirectoryIterator::SKIP_DOTS)
);
foreach ($iterator as $file) {
if ($file->isFile() && $file->getMTime() < $cutoffTime) {
$size = $file->getSize();
if (unlink($file->getPathname())) {
$deletedFiles++;
$deletedSize += $size;
}
}
}
}
return [
'deleted_files' => $deletedFiles,
'deleted_size' => $deletedSize,
'max_age_hours' => $maxAge
];
}
/**
* Cleanup old sessions
* @param array $data Configuration data
* @return array Cleanup result
*/
private function cleanupOldSessions($data)
{
$maxAge = $data['session_max_age_hours'] ?? 168; // 7 days
$cutoffTime = date('Y-m-d H:i:s', time() - ($maxAge * 3600));
try {
$db = $this->getDatabase();
// Cleanup database sessions if using database session storage
$query = "DELETE FROM db_sessions WHERE last_activity < ?";
$db->doQuery($query, [$cutoffTime]);
$deletedSessions = $db->getAffectedRows();
return [
'deleted_db_sessions' => $deletedSessions,
'max_age_hours' => $maxAge
];
} catch (Exception $e) {
$this->logError('Session cleanup failed', [
'error' => $e->getMessage()
]);
return [
'error' => $e->getMessage(),
'deleted_db_sessions' => 0
];
}
}
/**
* Cleanup failed uploads
* @param array $data Configuration data
* @return array Cleanup result
*/
private function cleanupFailedUploads($data)
{
$maxAge = $data['failed_upload_max_age_hours'] ?? 48;
$cutoffTime = date('Y-m-d H:i:s', time() - ($maxAge * 3600));
try {
$db = $this->getDatabase();
// Find failed uploads
$query = "SELECT file_key, usr_id FROM db_videofiles
WHERE processing_status = 'failed'
AND processed_at < ?";
$result = $db->doQuery($query, [$cutoffTime]);
$deletedRecords = 0;
$deletedFiles = 0;
$deletedSize = 0;
while ($row = $db->doFetch($result)) {
// Delete associated files
$userDir = _FPATH . 'f_data/media/' . $row['usr_id'] . '/';
$videoFiles = glob($userDir . $row['file_key'] . '*');
foreach ($videoFiles as $file) {
if (is_file($file)) {
$deletedSize += filesize($file);
if (unlink($file)) {
$deletedFiles++;
}
}
}
// Delete database record
$deleteQuery = "DELETE FROM db_videofiles WHERE file_key = ?";
$db->doQuery($deleteQuery, [$row['file_key']]);
$deletedRecords++;
}
return [
'deleted_records' => $deletedRecords,
'deleted_files' => $deletedFiles,
'deleted_size' => $deletedSize,
'max_age_hours' => $maxAge
];
} catch (Exception $e) {
$this->logError('Failed upload cleanup failed', [
'error' => $e->getMessage()
]);
return [
'error' => $e->getMessage(),
'deleted_records' => 0,
'deleted_files' => 0
];
}
}
/**
* Cleanup old analytics data
* @param array $data Configuration data
* @return array Cleanup result
*/
private function cleanupOldAnalytics($data)
{
$retentionDays = $data['analytics_retention_days'] ?? 90;
$cutoffTime = date('Y-m-d H:i:s', time() - ($retentionDays * 24 * 3600));
try {
$db = $this->getDatabase();
$deletedRecords = 0;
// Cleanup video analytics
$query = "DELETE FROM db_video_analytics WHERE created_at < ?";
$db->doQuery($query, [$cutoffTime]);
$deletedRecords += $db->getAffectedRows();
// Cleanup video views
$query = "DELETE FROM db_video_views WHERE created_at < ?";
$db->doQuery($query, [$cutoffTime]);
$deletedRecords += $db->getAffectedRows();
return [
'deleted_records' => $deletedRecords,
'retention_days' => $retentionDays
];
} catch (Exception $e) {
$this->logError('Analytics cleanup failed', [
'error' => $e->getMessage()
]);
return [
'error' => $e->getMessage(),
'deleted_records' => 0
];
}
}
/**
* Cleanup orphaned files
* @param array $data Configuration data
* @return array Cleanup result
*/
private function cleanupOrphanedFiles($data)
{
$mediaDir = _FPATH . 'f_data/media/';
$deletedFiles = 0;
$deletedSize = 0;
try {
if (!is_dir($mediaDir)) {
return [
'deleted_files' => 0,
'deleted_size' => 0,
'message' => 'Media directory not found'
];
}
// Get all file keys from database
$db = $this->getDatabase();
$query = "SELECT file_key FROM db_videofiles
UNION SELECT file_key FROM db_imagefiles
UNION SELECT file_key FROM db_audiofiles
UNION SELECT file_key FROM db_documentfiles";
$result = $db->doQuery($query);
$validKeys = [];
while ($row = $db->doFetch($result)) {
$validKeys[] = $row['file_key'];
}
// Scan media directory for orphaned files
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($mediaDir, RecursiveDirectoryIterator::SKIP_DOTS)
);
foreach ($iterator as $file) {
if ($file->isFile()) {
$filename = $file->getFilename();
// Extract file key from filename (assuming format: filekey.extension)
$fileKey = pathinfo($filename, PATHINFO_FILENAME);
if (!in_array($fileKey, $validKeys)) {
$size = $file->getSize();
if (unlink($file->getPathname())) {
$deletedFiles++;
$deletedSize += $size;
}
}
}
}
return [
'deleted_files' => $deletedFiles,
'deleted_size' => $deletedSize,
'scanned_keys' => count($validKeys)
];
} catch (Exception $e) {
$this->logError('Orphaned files cleanup failed', [
'error' => $e->getMessage()
]);
return [
'error' => $e->getMessage(),
'deleted_files' => 0,
'deleted_size' => 0
];
}
}
/**
* Optimize database
* @param array $data Configuration data
* @return array Optimization result
*/
private function optimizeDatabase($data)
{
try {
$db = $this->getDatabase();
$optimizedTables = 0;
// Get all tables
$query = "SHOW TABLES";
$result = $db->doQuery($query);
while ($row = $db->doFetch($result)) {
$table = array_values($row)[0];
// Optimize table
$optimizeQuery = "OPTIMIZE TABLE `{$table}`";
$db->doQuery($optimizeQuery);
$optimizedTables++;
}
return [
'optimized_tables' => $optimizedTables,
'message' => 'Database optimization completed'
];
} catch (Exception $e) {
$this->logError('Database optimization failed', [
'error' => $e->getMessage()
]);
return [
'error' => $e->getMessage(),
'optimized_tables' => 0
];
}
}
}

430
f_jobs/NotificationJob.php Normal file
View File

@@ -0,0 +1,430 @@
<?php
/*******************************************************************************************************************
| Enhanced Notification Job
| Handles various types of notifications (email, push, in-app)
|*******************************************************************************************************************/
class NotificationJob extends BaseJob
{
/**
* Handle notification sending
* @param array $data Notification data
* @return array Sending result
*/
public function handle($data)
{
$this->validateData($data, ['type', 'recipient']);
$type = $data['type'];
$recipient = $data['recipient'];
$subject = $data['subject'] ?? '';
$message = $data['message'] ?? '';
$templateData = $data['template_data'] ?? [];
$priority = $data['priority'] ?? 'normal';
$this->logProgress('Processing notification', [
'type' => $type,
'recipient' => $recipient,
'subject' => $subject,
'priority' => $priority
]);
try {
$result = [];
switch ($type) {
case 'email':
$result = $this->sendEmailNotification($recipient, $subject, $message, $templateData);
break;
case 'push':
$result = $this->sendPushNotification($recipient, $subject, $message, $templateData);
break;
case 'in_app':
$result = $this->sendInAppNotification($recipient, $subject, $message, $templateData);
break;
case 'sms':
$result = $this->sendSMSNotification($recipient, $message, $templateData);
break;
case 'webhook':
$result = $this->sendWebhookNotification($recipient, $data);
break;
default:
throw new Exception("Unknown notification type: {$type}");
}
// Log notification result
$this->logProgress('Notification sent', [
'type' => $type,
'recipient' => $recipient,
'success' => $result['success'],
'message_id' => $result['message_id'] ?? null
]);
// Update notification status in database
if (isset($data['notification_id'])) {
$this->updateNotificationStatus($data['notification_id'], $result['success'] ? 'sent' : 'failed', $result);
}
return $result;
} catch (Exception $e) {
$this->logError('Notification failed', [
'type' => $type,
'recipient' => $recipient,
'error' => $e->getMessage()
]);
// Update notification status as failed
if (isset($data['notification_id'])) {
$this->updateNotificationStatus($data['notification_id'], 'failed', ['error' => $e->getMessage()]);
}
throw $e;
}
}
/**
* Send email notification
* @param string $recipient Email address
* @param string $subject Email subject
* @param string $message Email message
* @param array $templateData Template data
* @return array Sending result
*/
private function sendEmailNotification($recipient, $subject, $message, $templateData)
{
try {
// Use existing email system or implement new one
$emailSender = new VEmailSender();
// Process template if provided
if (isset($templateData['template'])) {
$message = $this->processEmailTemplate($templateData['template'], $templateData);
}
$result = $emailSender->sendEmail($recipient, $subject, $message, [
'html' => $templateData['html'] ?? true,
'attachments' => $templateData['attachments'] ?? []
]);
return [
'success' => $result,
'message_id' => $emailSender->getLastMessageId(),
'type' => 'email'
];
} catch (Exception $e) {
return [
'success' => false,
'error' => $e->getMessage(),
'type' => 'email'
];
}
}
/**
* Send push notification
* @param string $recipient User ID or device token
* @param string $title Notification title
* @param string $message Notification message
* @param array $templateData Additional data
* @return array Sending result
*/
private function sendPushNotification($recipient, $title, $message, $templateData)
{
try {
// Implement push notification logic
$pushService = new VPushNotificationService();
$payload = [
'title' => $title,
'body' => $message,
'data' => $templateData['data'] ?? [],
'badge' => $templateData['badge'] ?? null,
'sound' => $templateData['sound'] ?? 'default'
];
$result = $pushService->sendNotification($recipient, $payload);
return [
'success' => $result['success'],
'message_id' => $result['message_id'] ?? null,
'type' => 'push'
];
} catch (Exception $e) {
return [
'success' => false,
'error' => $e->getMessage(),
'type' => 'push'
];
}
}
/**
* Send in-app notification
* @param string $recipient User ID
* @param string $title Notification title
* @param string $message Notification message
* @param array $templateData Additional data
* @return array Sending result
*/
private function sendInAppNotification($recipient, $title, $message, $templateData)
{
try {
$db = $this->getDatabase();
$notificationData = [
'user_id' => $recipient,
'title' => $title,
'message' => $message,
'type' => $templateData['notification_type'] ?? 'general',
'data' => json_encode($templateData['data'] ?? []),
'read_status' => 0,
'created_at' => date('Y-m-d H:i:s'),
'expires_at' => $templateData['expires_at'] ?? date('Y-m-d H:i:s', strtotime('+30 days'))
];
$notificationId = $db->doInsert('db_notifications', $notificationData);
// Send real-time notification if user is online
$this->sendRealTimeNotification($recipient, $notificationData);
return [
'success' => true,
'message_id' => $notificationId,
'type' => 'in_app'
];
} catch (Exception $e) {
return [
'success' => false,
'error' => $e->getMessage(),
'type' => 'in_app'
];
}
}
/**
* Send SMS notification
* @param string $recipient Phone number
* @param string $message SMS message
* @param array $templateData Template data
* @return array Sending result
*/
private function sendSMSNotification($recipient, $message, $templateData)
{
try {
// Implement SMS service integration (Twilio, AWS SNS, etc.)
$smsService = new VSMSService();
$result = $smsService->sendSMS($recipient, $message);
return [
'success' => $result['success'],
'message_id' => $result['message_id'] ?? null,
'type' => 'sms'
];
} catch (Exception $e) {
return [
'success' => false,
'error' => $e->getMessage(),
'type' => 'sms'
];
}
}
/**
* Send webhook notification
* @param string $recipient Webhook URL
* @param array $data Webhook data
* @return array Sending result
*/
private function sendWebhookNotification($recipient, $data)
{
try {
$payload = json_encode($data['payload'] ?? $data);
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $recipient,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'User-Agent: EasyStream-Webhook/1.0'
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_SSL_VERIFYPEER => false
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($error) {
throw new Exception("cURL error: {$error}");
}
$success = $httpCode >= 200 && $httpCode < 300;
return [
'success' => $success,
'http_code' => $httpCode,
'response' => $response,
'type' => 'webhook'
];
} catch (Exception $e) {
return [
'success' => false,
'error' => $e->getMessage(),
'type' => 'webhook'
];
}
}
/**
* Process email template
* @param string $template Template name
* @param array $data Template data
* @return string Processed template
*/
private function processEmailTemplate($template, $data)
{
$templateFile = _FPATH . "f_templates/email/{$template}.tpl";
if (!file_exists($templateFile)) {
return $data['message'] ?? '';
}
$content = file_get_contents($templateFile);
// Simple template variable replacement
foreach ($data as $key => $value) {
if (is_string($value) || is_numeric($value)) {
$content = str_replace("{{$key}}", $value, $content);
}
}
return $content;
}
/**
* Send real-time notification to online user
* @param string $userId User ID
* @param array $notificationData Notification data
*/
private function sendRealTimeNotification($userId, $notificationData)
{
try {
// Implement WebSocket or Server-Sent Events notification
// This is a placeholder for real-time notification system
$redis = VRedis::getInstance();
if ($redis->isConnected()) {
$channel = "user_notifications_{$userId}";
$redis->publish($channel, json_encode($notificationData));
}
} catch (Exception $e) {
// Log but don't fail the job
$this->logError('Real-time notification failed', [
'user_id' => $userId,
'error' => $e->getMessage()
]);
}
}
/**
* Update notification status in database
* @param int $notificationId Notification ID
* @param string $status Status
* @param array $result Result data
*/
private function updateNotificationStatus($notificationId, $status, $result)
{
try {
$db = $this->getDatabase();
$updateData = [
'status' => $status,
'sent_at' => date('Y-m-d H:i:s'),
'result_data' => json_encode($result)
];
$db->doUpdate('db_notification_queue', 'id', $updateData, $notificationId);
} catch (Exception $e) {
$this->logError('Failed to update notification status', [
'notification_id' => $notificationId,
'status' => $status,
'error' => $e->getMessage()
]);
}
}
}
/**
* Placeholder classes for services that need to be implemented
*/
class VEmailSender
{
private $lastMessageId;
public function sendEmail($recipient, $subject, $message, $options = [])
{
// Implement actual email sending logic
// This could use PHPMailer, SwiftMailer, or native mail()
$headers = "From: noreply@" . $_SERVER['HTTP_HOST'] . "\r\n";
$headers .= "Reply-To: noreply@" . $_SERVER['HTTP_HOST'] . "\r\n";
if ($options['html'] ?? false) {
$headers .= "Content-Type: text/html; charset=UTF-8\r\n";
}
$result = mail($recipient, $subject, $message, $headers);
$this->lastMessageId = uniqid('email_', true);
return $result;
}
public function getLastMessageId()
{
return $this->lastMessageId;
}
}
class VPushNotificationService
{
public function sendNotification($recipient, $payload)
{
// Implement push notification service (Firebase, APNs, etc.)
return [
'success' => true,
'message_id' => uniqid('push_', true)
];
}
}
class VSMSService
{
public function sendSMS($recipient, $message)
{
// Implement SMS service (Twilio, AWS SNS, etc.)
return [
'success' => true,
'message_id' => uniqid('sms_', true)
];
}
}

139
f_jobs/SendEmailJob.php Normal file
View File

@@ -0,0 +1,139 @@
<?php
/*******************************************************************************************************************
| Send Email Job
| Handles email sending in the background
|*******************************************************************************************************************/
class SendEmailJob extends BaseJob
{
/**
* Handle email sending
* @param array $data Email data
* @return bool Success status
*/
public function handle($data)
{
$this->validateData($data, ['to', 'subject', 'message']);
$to = $data['to'];
$subject = $data['subject'];
$message = $data['message'];
$from = $data['from'] ?? 'noreply@easystream.com';
$headers = $data['headers'] ?? [];
$this->logProgress('Starting email send', [
'to' => $to,
'subject' => $subject,
'from' => $from
]);
try {
// Prepare headers
$emailHeaders = [
'From: ' . $from,
'Reply-To: ' . $from,
'Content-Type: text/html; charset=UTF-8',
'X-Mailer: EasyStream'
];
// Add custom headers
foreach ($headers as $header) {
$emailHeaders[] = $header;
}
$headerString = implode("\r\n", $emailHeaders);
// Send email
$success = mail($to, $subject, $message, $headerString);
if ($success) {
$this->logProgress('Email sent successfully', [
'to' => $to,
'subject' => $subject
]);
// Log to database for tracking
$this->logEmailToDatabase($to, $subject, 'sent');
return true;
} else {
throw new Exception('mail() function returned false');
}
} catch (Exception $e) {
$this->logError('Failed to send email', [
'to' => $to,
'subject' => $subject,
'error' => $e->getMessage()
]);
// Log failed email to database
$this->logEmailToDatabase($to, $subject, 'failed', $e->getMessage());
throw $e;
}
}
/**
* Log email to database for tracking
* @param string $to Recipient
* @param string $subject Subject
* @param string $status Status (sent/failed)
* @param string $error Error message if failed
*/
private function logEmailToDatabase($to, $subject, $status, $error = null)
{
try {
$db = $this->getDatabase();
$emailLog = [
'recipient' => $to,
'subject' => $subject,
'status' => $status,
'error_message' => $error,
'sent_at' => date('Y-m-d H:i:s'),
'job_class' => get_class($this)
];
// Create table if it doesn't exist
$this->createEmailLogTable();
$db->doInsert('db_email_log', $emailLog);
} catch (Exception $e) {
$this->logError('Failed to log email to database', [
'error' => $e->getMessage()
]);
}
}
/**
* Create email log table if it doesn't exist
*/
private function createEmailLogTable()
{
global $db;
$sql = "CREATE TABLE IF NOT EXISTS `db_email_log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`recipient` varchar(255) NOT NULL,
`subject` varchar(500) NOT NULL,
`status` enum('sent','failed') NOT NULL,
`error_message` text,
`sent_at` datetime NOT NULL,
`job_class` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_recipient` (`recipient`),
KEY `idx_status` (`status`),
KEY `idx_sent_at` (`sent_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";
try {
$db->Execute($sql);
} catch (Exception $e) {
$this->logError('Failed to create email log table', [
'error' => $e->getMessage()
]);
}
}
}

View File

@@ -0,0 +1,385 @@
<?php
/*******************************************************************************************************************
| Send Notification Job
| Handles sending notifications to users
|*******************************************************************************************************************/
class SendNotificationJob extends BaseJob
{
/**
* Handle notification sending
* @param array $data Notification data
* @return bool Success status
*/
public function handle($data)
{
$this->validateData($data, ['user_id', 'type', 'message']);
$userId = $data['user_id'];
$type = $data['type'];
$message = $data['message'];
$title = $data['title'] ?? 'EasyStream Notification';
$actionUrl = $data['action_url'] ?? null;
$metadata = $data['metadata'] ?? [];
$this->logProgress('Sending notification', [
'user_id' => $userId,
'type' => $type,
'title' => $title
]);
try {
// Store notification in database
$notificationId = $this->storeNotification($userId, $type, $title, $message, $actionUrl, $metadata);
// Send real-time notification if user is online
$this->sendRealTimeNotification($userId, $notificationId, $type, $title, $message, $actionUrl);
// Send email notification if enabled for user
$this->sendEmailNotification($userId, $type, $title, $message, $actionUrl);
// Send push notification if user has enabled it
$this->sendPushNotification($userId, $type, $title, $message, $actionUrl);
$this->logProgress('Notification sent successfully', [
'user_id' => $userId,
'notification_id' => $notificationId,
'type' => $type
]);
return true;
} catch (Exception $e) {
$this->logError('Failed to send notification', [
'user_id' => $userId,
'type' => $type,
'error' => $e->getMessage()
]);
throw $e;
}
}
/**
* Store notification in database
* @param int $userId User ID
* @param string $type Notification type
* @param string $title Title
* @param string $message Message
* @param string $actionUrl Action URL
* @param array $metadata Metadata
* @return int Notification ID
*/
private function storeNotification($userId, $type, $title, $message, $actionUrl, $metadata)
{
$db = $this->getDatabase();
// Create table if it doesn't exist
$this->createNotificationTable();
$notificationData = [
'user_id' => $userId,
'type' => $type,
'title' => $title,
'message' => $message,
'action_url' => $actionUrl,
'metadata' => json_encode($metadata),
'is_read' => 0,
'created_at' => date('Y-m-d H:i:s')
];
$success = $db->doInsert('db_notifications', $notificationData);
if (!$success) {
throw new Exception('Failed to store notification in database');
}
// Get the inserted notification ID
global $db as $adodb;
return $adodb->Insert_ID();
}
/**
* Send real-time notification via WebSocket/Server-Sent Events
* @param int $userId User ID
* @param int $notificationId Notification ID
* @param string $type Type
* @param string $title Title
* @param string $message Message
* @param string $actionUrl Action URL
*/
private function sendRealTimeNotification($userId, $notificationId, $type, $title, $message, $actionUrl)
{
try {
$redis = $this->getRedis();
$notificationData = [
'id' => $notificationId,
'type' => $type,
'title' => $title,
'message' => $message,
'action_url' => $actionUrl,
'timestamp' => time()
];
// Store in Redis for real-time delivery
$redis->lpush("notifications:user:{$userId}", json_encode($notificationData));
// Limit to last 50 notifications per user
$redis->getRedis()->ltrim("notifications:user:{$userId}", 0, 49);
// Publish to notification channel for WebSocket delivery
$redis->getRedis()->publish("notifications", json_encode([
'user_id' => $userId,
'notification' => $notificationData
]));
$this->logProgress('Real-time notification queued', [
'user_id' => $userId,
'notification_id' => $notificationId
]);
} catch (Exception $e) {
$this->logError('Failed to send real-time notification', [
'user_id' => $userId,
'error' => $e->getMessage()
]);
}
}
/**
* Send email notification if user has enabled it
* @param int $userId User ID
* @param string $type Type
* @param string $title Title
* @param string $message Message
* @param string $actionUrl Action URL
*/
private function sendEmailNotification($userId, $type, $title, $message, $actionUrl)
{
try {
// Check if user wants email notifications for this type
if (!$this->userWantsEmailNotification($userId, $type)) {
return;
}
// Get user email
$userEmail = $this->getUserEmail($userId);
if (!$userEmail) {
return;
}
// Prepare email content
$emailSubject = "EasyStream: {$title}";
$emailMessage = $this->buildEmailTemplate($title, $message, $actionUrl);
// Queue email job
$queue = new VQueue();
$queue->enqueue('SendEmailJob', [
'to' => $userEmail,
'subject' => $emailSubject,
'message' => $emailMessage,
'from' => 'notifications@easystream.com'
], 'email');
$this->logProgress('Email notification queued', [
'user_id' => $userId,
'email' => $userEmail,
'type' => $type
]);
} catch (Exception $e) {
$this->logError('Failed to queue email notification', [
'user_id' => $userId,
'error' => $e->getMessage()
]);
}
}
/**
* Send push notification
* @param int $userId User ID
* @param string $type Type
* @param string $title Title
* @param string $message Message
* @param string $actionUrl Action URL
*/
private function sendPushNotification($userId, $type, $title, $message, $actionUrl)
{
try {
// Get user's push notification tokens
$pushTokens = $this->getUserPushTokens($userId);
if (empty($pushTokens)) {
return;
}
// Queue push notification job for each token
$queue = new VQueue();
foreach ($pushTokens as $token) {
$queue->enqueue('SendPushNotificationJob', [
'token' => $token,
'title' => $title,
'message' => $message,
'action_url' => $actionUrl,
'user_id' => $userId
], 'notifications');
}
$this->logProgress('Push notifications queued', [
'user_id' => $userId,
'token_count' => count($pushTokens),
'type' => $type
]);
} catch (Exception $e) {
$this->logError('Failed to queue push notifications', [
'user_id' => $userId,
'error' => $e->getMessage()
]);
}
}
/**
* Check if user wants email notifications for this type
* @param int $userId User ID
* @param string $type Notification type
* @return bool
*/
private function userWantsEmailNotification($userId, $type)
{
try {
$db = $this->getDatabase();
// Check user notification preferences
$preference = $db->singleFieldValue('db_user_preferences', 'email_notifications', 'user_id', $userId);
if ($preference === null) {
return true; // Default to enabled
}
$preferences = json_decode($preference, true);
return isset($preferences[$type]) ? $preferences[$type] : true;
} catch (Exception $e) {
return true; // Default to enabled on error
}
}
/**
* Get user email address
* @param int $userId User ID
* @return string|null Email address
*/
private function getUserEmail($userId)
{
try {
$db = $this->getDatabase();
return $db->singleFieldValue('db_accountuser', 'usr_email', 'usr_id', $userId);
} catch (Exception $e) {
return null;
}
}
/**
* Get user push notification tokens
* @param int $userId User ID
* @return array Push tokens
*/
private function getUserPushTokens($userId)
{
try {
$db = $this->getDatabase();
// This would need a push_tokens table
// For now, return empty array
return [];
} catch (Exception $e) {
return [];
}
}
/**
* Build email template
* @param string $title Title
* @param string $message Message
* @param string $actionUrl Action URL
* @return string HTML email content
*/
private function buildEmailTemplate($title, $message, $actionUrl)
{
$actionButton = '';
if ($actionUrl) {
$actionButton = '<p style="text-align: center; margin: 30px 0;">
<a href="' . htmlspecialchars($actionUrl) . '"
style="background: #4A90E2; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; display: inline-block;">
View Details
</a>
</p>';
}
return '
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>' . htmlspecialchars($title) . '</title>
</head>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;">
<div style="text-align: center; margin-bottom: 30px;">
<h1 style="color: #4A90E2;">EasyStream</h1>
</div>
<h2 style="color: #333;">' . htmlspecialchars($title) . '</h2>
<div style="background: #f9f9f9; padding: 20px; border-radius: 5px; margin: 20px 0;">
' . nl2br(htmlspecialchars($message)) . '
</div>
' . $actionButton . '
<hr style="border: none; border-top: 1px solid #eee; margin: 30px 0;">
<p style="font-size: 12px; color: #666; text-align: center;">
This is an automated message from EasyStream. Please do not reply to this email.
</p>
</body>
</html>';
}
/**
* Create notification table if it doesn't exist
*/
private function createNotificationTable()
{
global $db;
$sql = "CREATE TABLE IF NOT EXISTS `db_notifications` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`type` varchar(50) NOT NULL,
`title` varchar(255) NOT NULL,
`message` text NOT NULL,
`action_url` varchar(500) DEFAULT NULL,
`metadata` text,
`is_read` tinyint(1) NOT NULL DEFAULT 0,
`created_at` datetime NOT NULL,
`read_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_type` (`type`),
KEY `idx_is_read` (`is_read`),
KEY `idx_created_at` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";
try {
$db->Execute($sql);
} catch (Exception $e) {
$this->logError('Failed to create notification table', [
'error' => $e->getMessage()
]);
}
}
}

View File

@@ -0,0 +1,293 @@
<?php
/*******************************************************************************************************************
| Thumbnail Generation Job
| Handles thumbnail generation for videos and images
|*******************************************************************************************************************/
class ThumbnailGenerationJob extends BaseJob
{
/**
* Handle thumbnail generation
* @param array $data Thumbnail generation data
* @return array Generation result
*/
public function handle($data)
{
$this->validateData($data, ['file_key', 'file_path', 'file_type']);
$fileKey = $data['file_key'];
$filePath = $data['file_path'];
$fileType = $data['file_type'];
$sizes = $data['sizes'] ?? ['small' => [160, 120], 'medium' => [320, 240], 'large' => [640, 480]];
$this->logProgress('Starting thumbnail generation', [
'file_key' => $fileKey,
'file_path' => $filePath,
'file_type' => $fileType,
'sizes' => array_keys($sizes)
]);
try {
if (!file_exists($filePath)) {
throw new Exception("Source file not found: {$filePath}");
}
$thumbnailDir = _FPATH . 'f_data/thumbnails/' . substr($fileKey, 0, 2) . '/' . $fileKey . '/';
if (!is_dir($thumbnailDir)) {
mkdir($thumbnailDir, 0755, true);
}
$results = [];
if ($fileType === 'video') {
$results = $this->generateVideoThumbnails($filePath, $thumbnailDir, $fileKey, $sizes);
} elseif (in_array($fileType, ['image', 'photo'])) {
$results = $this->generateImageThumbnails($filePath, $thumbnailDir, $fileKey, $sizes);
} else {
throw new Exception("Unsupported file type for thumbnail generation: {$fileType}");
}
// Update database with thumbnail information
$this->updateThumbnailRecord($fileKey, $results);
$this->logProgress('Thumbnail generation completed', [
'file_key' => $fileKey,
'thumbnails_generated' => count($results)
]);
return [
'file_key' => $fileKey,
'thumbnails' => $results,
'thumbnail_dir' => $thumbnailDir
];
} catch (Exception $e) {
$this->logError('Thumbnail generation failed', [
'file_key' => $fileKey,
'error' => $e->getMessage()
]);
throw $e;
}
}
/**
* Generate video thumbnails using FFmpeg
* @param string $videoPath Video file path
* @param string $outputDir Output directory
* @param string $fileKey File key
* @param array $sizes Thumbnail sizes
* @return array Generation results
*/
private function generateVideoThumbnails($videoPath, $outputDir, $fileKey, $sizes)
{
$ffmpegPath = $this->findFFmpegPath();
$results = [];
foreach ($sizes as $sizeName => $dimensions) {
$width = $dimensions[0];
$height = $dimensions[1];
$outputFile = $outputDir . $fileKey . "_{$sizeName}.jpg";
// Generate thumbnail at 10% of video duration
$command = sprintf(
'%s -i %s -ss 00:00:10 -vframes 1 -vf "scale=%d:%d:force_original_aspect_ratio=decrease,pad=%d:%d:(ow-iw)/2:(oh-ih)/2:color=black" -q:v 2 %s 2>&1',
escapeshellarg($ffmpegPath),
escapeshellarg($videoPath),
$width,
$height,
$width,
$height,
escapeshellarg($outputFile)
);
$output = [];
$returnCode = 0;
exec($command, $output, $returnCode);
if ($returnCode === 0 && file_exists($outputFile)) {
$results[$sizeName] = [
'success' => true,
'file' => $outputFile,
'size' => filesize($outputFile),
'width' => $width,
'height' => $height
];
} else {
$results[$sizeName] = [
'success' => false,
'error' => 'FFmpeg thumbnail generation failed',
'output' => implode("\n", $output)
];
}
}
return $results;
}
/**
* Generate image thumbnails using GD
* @param string $imagePath Image file path
* @param string $outputDir Output directory
* @param string $fileKey File key
* @param array $sizes Thumbnail sizes
* @return array Generation results
*/
private function generateImageThumbnails($imagePath, $outputDir, $fileKey, $sizes)
{
$results = [];
// Get image info
$imageInfo = getimagesize($imagePath);
if (!$imageInfo) {
throw new Exception("Unable to read image information");
}
$sourceWidth = $imageInfo[0];
$sourceHeight = $imageInfo[1];
$mimeType = $imageInfo['mime'];
// Create source image resource
switch ($mimeType) {
case 'image/jpeg':
$sourceImage = imagecreatefromjpeg($imagePath);
break;
case 'image/png':
$sourceImage = imagecreatefrompng($imagePath);
break;
case 'image/gif':
$sourceImage = imagecreatefromgif($imagePath);
break;
case 'image/webp':
$sourceImage = imagecreatefromwebp($imagePath);
break;
default:
throw new Exception("Unsupported image format: {$mimeType}");
}
if (!$sourceImage) {
throw new Exception("Failed to create image resource");
}
foreach ($sizes as $sizeName => $dimensions) {
$targetWidth = $dimensions[0];
$targetHeight = $dimensions[1];
// Calculate dimensions maintaining aspect ratio
$ratio = min($targetWidth / $sourceWidth, $targetHeight / $sourceHeight);
$newWidth = (int)($sourceWidth * $ratio);
$newHeight = (int)($sourceHeight * $ratio);
// Create thumbnail
$thumbnail = imagecreatetruecolor($targetWidth, $targetHeight);
// Handle transparency for PNG and GIF
if ($mimeType === 'image/png' || $mimeType === 'image/gif') {
imagealphablending($thumbnail, false);
imagesavealpha($thumbnail, true);
$transparent = imagecolorallocatealpha($thumbnail, 255, 255, 255, 127);
imagefill($thumbnail, 0, 0, $transparent);
} else {
// Fill with white background for JPEG
$white = imagecolorallocate($thumbnail, 255, 255, 255);
imagefill($thumbnail, 0, 0, $white);
}
// Calculate centering offsets
$offsetX = (int)(($targetWidth - $newWidth) / 2);
$offsetY = (int)(($targetHeight - $newHeight) / 2);
// Resize and copy
imagecopyresampled(
$thumbnail, $sourceImage,
$offsetX, $offsetY, 0, 0,
$newWidth, $newHeight, $sourceWidth, $sourceHeight
);
// Save thumbnail
$outputFile = $outputDir . $fileKey . "_{$sizeName}.jpg";
if (imagejpeg($thumbnail, $outputFile, 85)) {
$results[$sizeName] = [
'success' => true,
'file' => $outputFile,
'size' => filesize($outputFile),
'width' => $targetWidth,
'height' => $targetHeight
];
} else {
$results[$sizeName] = [
'success' => false,
'error' => 'Failed to save thumbnail'
];
}
imagedestroy($thumbnail);
}
imagedestroy($sourceImage);
return $results;
}
/**
* Update thumbnail record in database
* @param string $fileKey File key
* @param array $results Thumbnail generation results
*/
private function updateThumbnailRecord($fileKey, $results)
{
try {
$db = $this->getDatabase();
$thumbnailData = [];
foreach ($results as $sizeName => $result) {
if ($result['success']) {
$thumbnailData[$sizeName] = [
'file' => basename($result['file']),
'width' => $result['width'],
'height' => $result['height'],
'size' => $result['size']
];
}
}
$updateData = [
'thumbnails_generated' => 1,
'thumbnail_data' => json_encode($thumbnailData),
'thumbnail_updated' => date('Y-m-d H:i:s')
];
// Update appropriate table based on file type
$db->doUpdate('db_videofiles', 'file_key', $updateData, $fileKey);
$this->logProgress('Thumbnail record updated', [
'file_key' => $fileKey,
'thumbnails' => array_keys($thumbnailData)
]);
} catch (Exception $e) {
$this->logError('Failed to update thumbnail record', [
'file_key' => $fileKey,
'error' => $e->getMessage()
]);
}
}
/**
* Find FFmpeg path
* @return string FFmpeg path
*/
private function findFFmpegPath()
{
$paths = ['/usr/bin/ffmpeg', '/usr/local/bin/ffmpeg', 'ffmpeg'];
foreach ($paths as $path) {
if (shell_exec("which {$path}") || file_exists($path)) {
return $path;
}
}
return 'ffmpeg'; // Fallback to PATH
}
}

View File

@@ -0,0 +1,269 @@
<?php
/*******************************************************************************************************************
| Enhanced Video Processing Job
| Handles video transcoding and processing in the background with VVideoProcessor
|*******************************************************************************************************************/
class VideoProcessingJob extends BaseJob
{
private $videoProcessor;
public function __construct()
{
parent::__construct();
$this->videoProcessor = new VVideoProcessor();
}
/**
* Handle video processing using VVideoProcessor
* @param array $data Video processing data
* @return array Processing result
*/
public function handle($data)
{
$this->validateData($data, ['video_key', 'input_file']);
$videoKey = $data['video_key'];
$inputFile = $data['input_file'];
$options = [
'formats' => $data['formats'] ?? ['1080p', '720p', '480p', '360p'],
'generate_preview' => $data['generate_preview'] ?? true,
'generate_hls' => $data['generate_hls'] ?? true
];
$this->logProgress('Starting video processing', [
'video_key' => $videoKey,
'input_file' => $inputFile,
'options' => $options
]);
try {
// Process video using enhanced VVideoProcessor
$result = $this->videoProcessor->processVideo($inputFile, $videoKey, $options);
if ($result['success']) {
$this->logProgress('Video processing completed successfully', [
'video_key' => $videoKey,
'formats_processed' => array_keys($result['results']),
'video_info' => $result['video_info']
]);
// Send success notification to user
$this->sendUserNotification($videoKey, 'processing_completed', $result);
return $result;
} else {
throw new Exception($result['error'] ?? 'Video processing failed');
}
} catch (Exception $e) {
$this->logError('Video processing failed', [
'video_key' => $videoKey,
'error' => $e->getMessage()
]);
// Send failure notification to user
$this->sendUserNotification($videoKey, 'processing_failed', ['error' => $e->getMessage()]);
throw $e;
}
}
/**
* Process video to specific format
* @param string $inputFile Input file path
* @param string $outputDir Output directory
* @param string $format Format (720p, 480p, etc.)
* @param string $videoId Video ID
* @return array Processing result
*/
private function processVideoFormat($inputFile, $outputDir, $format, $videoId)
{
$outputFile = $outputDir . '/' . $videoId . '_' . $format . '.mp4';
// Define format settings
$formatSettings = [
'1080p' => ['width' => 1920, 'height' => 1080, 'bitrate' => '5000k'],
'720p' => ['width' => 1280, 'height' => 720, 'bitrate' => '2500k'],
'480p' => ['width' => 854, 'height' => 480, 'bitrate' => '1000k'],
'360p' => ['width' => 640, 'height' => 360, 'bitrate' => '750k'],
'240p' => ['width' => 426, 'height' => 240, 'bitrate' => '400k']
];
if (!isset($formatSettings[$format])) {
return [
'success' => false,
'error' => "Unknown format: {$format}",
'output_file' => null
];
}
$settings = $formatSettings[$format];
// Build FFmpeg command
$ffmpegCmd = sprintf(
'ffmpeg -i %s -vf "scale=%d:%d:force_original_aspect_ratio=decrease,pad=%d:%d:(ow-iw)/2:(oh-ih)/2" -c:v libx264 -b:v %s -c:a aac -b:a 128k -movflags +faststart %s 2>&1',
escapeshellarg($inputFile),
$settings['width'],
$settings['height'],
$settings['width'],
$settings['height'],
$settings['bitrate'],
escapeshellarg($outputFile)
);
$this->logProgress("Executing FFmpeg for {$format}", [
'command' => $ffmpegCmd,
'output_file' => $outputFile
]);
// Execute FFmpeg
$output = [];
$returnCode = 0;
exec($ffmpegCmd, $output, $returnCode);
if ($returnCode === 0 && file_exists($outputFile)) {
return [
'success' => true,
'output_file' => $outputFile,
'file_size' => filesize($outputFile),
'format' => $format
];
} else {
return [
'success' => false,
'error' => 'FFmpeg processing failed',
'return_code' => $returnCode,
'output' => implode("\n", $output),
'output_file' => $outputFile
];
}
}
/**
* Generate video thumbnail
* @param string $inputFile Input file path
* @param string $outputDir Output directory
* @param string $videoId Video ID
* @return array Generation result
*/
private function generateThumbnail($inputFile, $outputDir, $videoId)
{
$thumbnailFile = $outputDir . '/' . $videoId . '_thumb.jpg';
// Generate thumbnail at 10% of video duration
$ffmpegCmd = sprintf(
'ffmpeg -i %s -ss 00:00:10 -vframes 1 -vf "scale=320:240:force_original_aspect_ratio=decrease,pad=320:240:(ow-iw)/2:(oh-ih)/2" %s 2>&1',
escapeshellarg($inputFile),
escapeshellarg($thumbnailFile)
);
$output = [];
$returnCode = 0;
exec($ffmpegCmd, $output, $returnCode);
if ($returnCode === 0 && file_exists($thumbnailFile)) {
return [
'success' => true,
'thumbnail_file' => $thumbnailFile,
'file_size' => filesize($thumbnailFile)
];
} else {
return [
'success' => false,
'error' => 'Thumbnail generation failed',
'return_code' => $returnCode,
'output' => implode("\n", $output)
];
}
}
/**
* Send notification to user about processing status
* @param string $videoKey Video key
* @param string $type Notification type
* @param array $data Additional data
*/
private function sendUserNotification($videoKey, $type, $data = [])
{
try {
// Get video and user information
$db = $this->getDatabase();
$query = "SELECT vf.*, au.usr_user, au.usr_email
FROM db_videofiles vf
JOIN db_accountuser au ON vf.usr_id = au.usr_id
WHERE vf.file_key = ?";
$result = $db->doQuery($query, [$videoKey]);
$video = $db->doFetch($result);
if (!$video) {
return;
}
// Create notification
$notification = [
'user_id' => $video['usr_id'],
'type' => 'video_' . $type,
'title' => $this->getNotificationTitle($type),
'message' => $this->getNotificationMessage($type, $video['file_title']),
'data' => json_encode([
'video_key' => $videoKey,
'video_title' => $video['file_title'],
'processing_data' => $data
]),
'created_at' => date('Y-m-d H:i:s'),
'read_status' => 0
];
$db->doInsert('db_notifications', $notification);
$this->logProgress('User notification sent', [
'video_key' => $videoKey,
'user_id' => $video['usr_id'],
'type' => $type
]);
} catch (Exception $e) {
$this->logError('Failed to send user notification', [
'video_key' => $videoKey,
'type' => $type,
'error' => $e->getMessage()
]);
}
}
/**
* Get notification title based on type
* @param string $type Notification type
* @return string Title
*/
private function getNotificationTitle($type)
{
switch ($type) {
case 'processing_completed':
return 'Video Processing Complete';
case 'processing_failed':
return 'Video Processing Failed';
default:
return 'Video Update';
}
}
/**
* Get notification message based on type
* @param string $type Notification type
* @param string $videoTitle Video title
* @return string Message
*/
private function getNotificationMessage($type, $videoTitle)
{
switch ($type) {
case 'processing_completed':
return "Your video '{$videoTitle}' has been processed successfully and is now available for viewing.";
case 'processing_failed':
return "Processing failed for your video '{$videoTitle}'. Please try uploading again or contact support.";
default:
return "Update for your video '{$videoTitle}'.";
}
}
}