db = $class_database; $this->logger = new VLogger('notifications'); } /** * Create a new notification * * @param int $usr_id User receiving the notification * @param string $type Notification type (comment, like, subscribe, video_upload, mention, system) * @param string $title Notification title * @param string $message Optional detailed message * @param string $link Optional link URL * @param int $actor_id User who triggered the notification * @param int $related_video_id Related video ID * @param int $related_comment_id Related comment ID * @return int|false Notification ID or false on failure */ public function create($usr_id, $type, $title, $message = '', $link = '', $actor_id = null, $related_video_id = null, $related_comment_id = null) { // Don't notify users about their own actions if ($actor_id && $usr_id == $actor_id) { return false; } // Insert notification $sql = "INSERT INTO db_notifications (usr_id, type, title, message, link, actor_id, related_video_id, related_comment_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"; $result = $this->db->execute($sql, [ $usr_id, $type, $title, $message, $link, $actor_id, $related_video_id, $related_comment_id ]); if (!$result) { $this->logger->error("Failed to create notification for user $usr_id"); return false; } $notification_id = $this->db->lastInsertId(); // Queue email if user preferences allow $this->queueEmail($usr_id, $type, $title, $message, $link); $this->logger->info("Created notification $notification_id for user $usr_id (type: $type)"); return $notification_id; } /** * Get unread notification count for a user */ public function getUnreadCount($usr_id) { $sql = "SELECT COUNT(*) as count FROM db_notifications WHERE usr_id = ? AND is_read = 0"; $result = $this->db->execute($sql, [$usr_id]); if (!$result) { return 0; } $row = $this->db->fetch($result); return intval($row['count'] ?? 0); } /** * Get notifications for a user */ public function getUserNotifications($usr_id, $limit = 20, $offset = 0, $unread_only = false) { $where = $unread_only ? "AND is_read = 0" : ""; $sql = "SELECT n.*, u.usr_user as actor_username, u.usr_avatar as actor_avatar FROM db_notifications n LEFT JOIN db_accountuser u ON n.actor_id = u.usr_id WHERE n.usr_id = ? $where ORDER BY n.created_at DESC LIMIT ? OFFSET ?"; $result = $this->db->execute($sql, [$usr_id, $limit, $offset]); if (!$result) { return []; } return $this->db->resultsToArray($result); } /** * Mark notification(s) as read */ public function markAsRead($notification_id, $usr_id = null) { if (is_array($notification_id)) { $ids = implode(',', array_map('intval', $notification_id)); $sql = "UPDATE db_notifications SET is_read = 1, read_at = NOW() WHERE notification_id IN ($ids)"; if ($usr_id) { $sql .= " AND usr_id = " . intval($usr_id); } } else { $sql = "UPDATE db_notifications SET is_read = 1, read_at = NOW() WHERE notification_id = " . intval($notification_id); if ($usr_id) { $sql .= " AND usr_id = " . intval($usr_id); } } return $this->db->execute($sql); } /** * Mark all notifications as read for a user */ public function markAllAsRead($usr_id) { $sql = "UPDATE db_notifications SET is_read = 1, read_at = NOW() WHERE usr_id = ? AND is_read = 0"; return $this->db->execute($sql, [$usr_id]); } /** * Mark notifications as seen (displayed to user, but not necessarily read) */ public function markAsSeen($usr_id) { $sql = "UPDATE db_notifications SET is_seen = 1 WHERE usr_id = ? AND is_seen = 0"; return $this->db->execute($sql, [$usr_id]); } /** * Delete a notification */ public function delete($notification_id, $usr_id) { $sql = "DELETE FROM db_notifications WHERE notification_id = ? AND usr_id = ?"; return $this->db->execute($sql, [$notification_id, $usr_id]); } /** * Get user notification preferences */ public function getPreferences($usr_id) { $sql = "SELECT * FROM db_notification_preferences WHERE usr_id = ?"; $result = $this->db->execute($sql, [$usr_id]); if (!$result || $this->db->rowCount($result) == 0) { // Create default preferences $this->createDefaultPreferences($usr_id); return $this->getPreferences($usr_id); } return $this->db->fetch($result); } /** * Update user notification preferences */ public function updatePreferences($usr_id, $preferences) { $allowed_fields = [ 'email_comments', 'email_likes', 'email_subscribes', 'email_uploads', 'email_mentions', 'email_digest', 'push_enabled', 'push_comments', 'push_likes', 'push_subscribes' ]; $sets = []; $values = []; foreach ($preferences as $field => $value) { if (in_array($field, $allowed_fields)) { $sets[] = "$field = ?"; $values[] = $value; } } if (empty($sets)) { return false; } $values[] = $usr_id; $sql = "UPDATE db_notification_preferences SET " . implode(', ', $sets) . " WHERE usr_id = ?"; return $this->db->execute($sql, $values); } /** * Create default notification preferences for a user */ private function createDefaultPreferences($usr_id) { $sql = "INSERT INTO db_notification_preferences (usr_id) VALUES (?)"; return $this->db->execute($sql, [$usr_id]); } /** * Queue email notification */ private function queueEmail($usr_id, $type, $title, $message, $link) { // Get user preferences $prefs = $this->getPreferences($usr_id); // Check if email is enabled for this type $email_field = "email_" . str_replace('_', 's', $type); if (isset($prefs[$email_field]) && !$prefs[$email_field]) { return false; } // Get user email $sql = "SELECT usr_email FROM db_accountuser WHERE usr_id = ?"; $result = $this->db->execute($sql, [$usr_id]); if (!$result) { return false; } $user = $this->db->fetch($result); if (!$user || empty($user['usr_email'])) { return false; } // Determine send time based on digest preference $scheduled_for = 'NOW()'; if ($prefs['email_digest'] === 'hourly') { $scheduled_for = "DATE_ADD(NOW(), INTERVAL 1 HOUR)"; } elseif ($prefs['email_digest'] === 'daily') { $scheduled_for = "DATE_ADD(CURDATE(), INTERVAL 1 DAY)"; // Tomorrow at midnight } elseif ($prefs['email_digest'] === 'weekly') { $scheduled_for = "DATE_ADD(CURDATE(), INTERVAL (7 - WEEKDAY(CURDATE())) DAY)"; // Next Monday } elseif ($prefs['email_digest'] === 'never') { return false; } // Build email body $email_body = $this->buildEmailBody($title, $message, $link); // Queue email $sql = "INSERT INTO db_email_queue (usr_id, email, subject, body, scheduled_for) VALUES (?, ?, ?, ?, $scheduled_for)"; return $this->db->execute($sql, [ $usr_id, $user['usr_email'], $title, $email_body ]); } /** * Build HTML email body */ private function buildEmailBody($title, $message, $link) { $html = "
"; $html .= "" . nl2br(htmlspecialchars($message)) . "
"; } if ($link) { $base_url = $_SERVER['HTTP_HOST'] ?? 'localhost'; $full_link = "http://$base_url$link"; $html .= ""; } $html .= "You received this email because you have notifications enabled on EasyStream. Manage your notification preferences.
"; $html .= ""; return $html; } /** * Process email queue (called by cron) */ public function processEmailQueue($limit = 50) { $sql = "SELECT * FROM db_email_queue WHERE sent_at IS NULL AND scheduled_for <= NOW() AND attempts < 3 ORDER BY scheduled_for ASC LIMIT $limit"; $result = $this->db->execute($sql); if (!$result) { return 0; } $emails = $this->db->resultsToArray($result); $sent_count = 0; foreach ($emails as $email) { if ($this->sendEmail($email)) { $sent_count++; // Mark as sent $this->db->execute( "UPDATE db_email_queue SET sent_at = NOW() WHERE queue_id = ?", [$email['queue_id']] ); // Mark notification as emailed $this->db->execute( "UPDATE db_notifications SET email_sent = 1 WHERE usr_id = ? AND email_sent = 0 AND created_at >= ?", [$email['usr_id'], $email['created_at']] ); } else { // Increment attempts $this->db->execute( "UPDATE db_email_queue SET attempts = attempts + 1, last_error = ? WHERE queue_id = ?", ['Failed to send email', $email['queue_id']] ); } } $this->logger->info("Processed email queue: $sent_count sent out of " . count($emails) . " pending"); return $sent_count; } /** * Send an email */ private function sendEmail($email) { // Use PHP mail() function // In production, integrate with SendGrid/Mailgun/etc. $headers = "From: EasyStream