Files
easystream-main/f_modules/m_frontend/m_notif/notifications_bell.php
SamiAhmed7777 0b7e2d0a5b 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
2025-10-21 00:39:45 -07:00

588 lines
16 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
/**
* Notifications Bell Component
*
* Provides a dropdown notification interface similar to YouTube's notification bell
* Features:
* - Real-time notification count
* - Dropdown list of recent notifications
* - Mark as read/unread
* - Mark all as read
* - Notification types: comments, likes, subscriptions, uploads, etc.
*
* This file can be included in the header template
*/
define('_ISVALID', true);
// AJAX Handlers
if (isset($_POST['action'])) {
$action = VSecurity::postParam('action', 'string');
switch ($action) {
case 'get_notifications':
echo json_encode(getNotifications());
exit;
case 'mark_read':
$notif_id = VSecurity::postParam('notif_id', 'int');
echo json_encode(markAsRead($notif_id));
exit;
case 'mark_all_read':
echo json_encode(markAllAsRead());
exit;
case 'get_count':
echo json_encode(['count' => getUnreadCount()]);
exit;
}
}
/**
* Get notifications for current user
*/
function getNotifications($limit = 20)
{
global $class_database, $cfg;
$usr_id = isset($_SESSION['USER_ID']) ? (int) $_SESSION['USER_ID'] : 0;
if ($usr_id == 0) {
return ['notifications' => [], 'unread_count' => 0];
}
$sql = "SELECT
n.*,
u.usr_user, u.usr_dname, u.usr_key
FROM `db_notifications` n
LEFT JOIN `db_accountuser` u ON n.from_usr_id = u.usr_id
WHERE n.to_usr_id = %d
ORDER BY n.db_id DESC
LIMIT %d";
$result = $class_database->doQuery($sql, $usr_id, $limit);
$notifications = [];
while ($row = $result->fetch_assoc()) {
$notifications[] = [
'id' => $row['db_id'],
'type' => $row['notif_type'],
'message' => $row['notif_message'],
'from_user' => $row['usr_dname'] ?: 'System',
'from_username' => $row['usr_user'],
'from_key' => $row['usr_key'],
'link' => $row['notif_link'],
'is_read' => $row['notif_read'] == 1,
'date' => $row['notif_date'],
'time_ago' => timeAgo($row['notif_date'])
];
}
return [
'notifications' => $notifications,
'unread_count' => getUnreadCount()
];
}
/**
* Get unread notification count
*/
function getUnreadCount()
{
global $class_database;
$usr_id = isset($_SESSION['USER_ID']) ? (int) $_SESSION['USER_ID'] : 0;
if ($usr_id == 0) {
return 0;
}
$sql = "SELECT COUNT(*) as count FROM `db_notifications`
WHERE `to_usr_id` = %d AND `notif_read` = 0";
$result = $class_database->doQuery($sql, $usr_id);
$row = $result->fetch_assoc();
return (int) $row['count'];
}
/**
* Mark notification as read
*/
function markAsRead($notif_id)
{
global $class_database;
$usr_id = isset($_SESSION['USER_ID']) ? (int) $_SESSION['USER_ID'] : 0;
$sql = "UPDATE `db_notifications` SET `notif_read` = 1
WHERE `db_id` = %d AND `to_usr_id` = %d";
$class_database->doQuery($sql, $notif_id, $usr_id);
return ['success' => true, 'unread_count' => getUnreadCount()];
}
/**
* Mark all notifications as read
*/
function markAllAsRead()
{
global $class_database;
$usr_id = isset($_SESSION['USER_ID']) ? (int) $_SESSION['USER_ID'] : 0;
$sql = "UPDATE `db_notifications` SET `notif_read` = 1
WHERE `to_usr_id` = %d AND `notif_read` = 0";
$class_database->doQuery($sql, $usr_id);
return ['success' => true, 'unread_count' => 0];
}
/**
* Format time ago
*/
function timeAgo($datetime)
{
$timestamp = strtotime($datetime);
$diff = time() - $timestamp;
if ($diff < 60) {
return 'just now';
} elseif ($diff < 3600) {
$mins = floor($diff / 60);
return $mins . ' minute' . ($mins > 1 ? 's' : '') . ' ago';
} elseif ($diff < 86400) {
$hours = floor($diff / 3600);
return $hours . ' hour' . ($hours > 1 ? 's' : '') . ' ago';
} elseif ($diff < 604800) {
$days = floor($diff / 86400);
return $days . ' day' . ($days > 1 ? 's' : '') . ' ago';
} elseif ($diff < 2592000) {
$weeks = floor($diff / 604800);
return $weeks . ' week' . ($weeks > 1 ? 's' : '') . ' ago';
} else {
return date('M d, Y', $timestamp);
}
}
// If not an AJAX request, return the HTML component
if (!isset($_POST['action'])):
$unread_count = getUnreadCount();
?>
<!-- Notification Bell Component -->
<div class="notification-bell-wrapper">
<button class="notification-bell-button" id="notificationBell" aria-label="Notifications">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/>
<path d="M13.73 21a2 2 0 0 1-3.46 0"/>
</svg>
<?php if ($unread_count > 0): ?>
<span class="notification-badge" id="notificationBadge"><?php echo $unread_count > 99 ? '99+' : $unread_count; ?></span>
<?php endif; ?>
</button>
<div class="notification-dropdown" id="notificationDropdown">
<div class="notification-header">
<h3>Notifications</h3>
<button class="mark-all-read" id="markAllRead">Mark all as read</button>
</div>
<div class="notification-list" id="notificationList">
<div class="loading">Loading notifications...</div>
</div>
</div>
</div>
<style>
.notification-bell-wrapper {
position: relative;
display: inline-block;
}
.notification-bell-button {
position: relative;
background: none;
border: none;
cursor: pointer;
padding: 8px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #030303;
transition: background-color 0.2s;
}
.notification-bell-button:hover {
background-color: rgba(0, 0, 0, 0.1);
}
.notification-bell-button svg {
width: 24px;
height: 24px;
}
.notification-badge {
position: absolute;
top: 4px;
right: 4px;
background: #cc0000;
color: white;
font-size: 11px;
font-weight: 600;
padding: 2px 5px;
border-radius: 10px;
min-width: 18px;
text-align: center;
line-height: 1.2;
}
.notification-dropdown {
position: absolute;
top: 100%;
right: 0;
margin-top: 8px;
width: 400px;
max-height: 600px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
display: none;
flex-direction: column;
z-index: 1000;
}
.notification-dropdown.show {
display: flex;
}
.notification-header {
padding: 16px 20px;
border-bottom: 1px solid #e0e0e0;
display: flex;
justify-content: space-between;
align-items: center;
}
.notification-header h3 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.mark-all-read {
background: none;
border: none;
color: #065fd4;
cursor: pointer;
font-size: 13px;
padding: 4px 8px;
}
.mark-all-read:hover {
text-decoration: underline;
}
.notification-list {
overflow-y: auto;
max-height: 540px;
}
.loading {
padding: 40px 20px;
text-align: center;
color: #606060;
}
.notification-item {
padding: 12px 20px;
border-bottom: 1px solid #f0f0f0;
display: flex;
gap: 12px;
cursor: pointer;
transition: background-color 0.2s;
text-decoration: none;
color: inherit;
}
.notification-item:hover {
background-color: #f9f9f9;
}
.notification-item.unread {
background-color: #e8f0fe;
}
.notification-item.unread:hover {
background-color: #d2e3fc;
}
.notification-icon {
width: 40px;
height: 40px;
border-radius: 50%;
background: #065fd4;
color: white;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.notification-content {
flex: 1;
min-width: 0;
}
.notification-message {
font-size: 14px;
line-height: 1.4;
margin-bottom: 4px;
}
.notification-time {
font-size: 12px;
color: #606060;
}
.notification-unread-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #065fd4;
flex-shrink: 0;
margin-top: 6px;
}
.empty-notifications {
padding: 60px 20px;
text-align: center;
}
.empty-notifications svg {
width: 80px;
height: 80px;
color: #909090;
margin-bottom: 16px;
}
.empty-notifications p {
color: #606060;
font-size: 14px;
}
/* Dark mode */
@media (prefers-color-scheme: dark) {
.notification-bell-button {
color: #f1f1f1;
}
.notification-bell-button:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.notification-dropdown {
background: #212121;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.5);
}
.notification-header {
border-bottom-color: #303030;
}
.notification-item {
border-bottom-color: #303030;
}
.notification-item:hover {
background-color: #2a2a2a;
}
.notification-item.unread {
background-color: #1a3a52;
}
.notification-item.unread:hover {
background-color: #1e4a62;
}
}
/* Mobile responsive */
@media (max-width: 480px) {
.notification-dropdown {
width: 100vw;
right: -20px;
max-height: 80vh;
border-radius: 12px 12px 0 0;
}
}
</style>
<script>
// Notification Bell JavaScript
(function() {
const bell = document.getElementById('notificationBell');
const dropdown = document.getElementById('notificationDropdown');
const notificationList = document.getElementById('notificationList');
const badge = document.getElementById('notificationBadge');
const markAllReadBtn = document.getElementById('markAllRead');
// Toggle dropdown
bell.addEventListener('click', function(e) {
e.stopPropagation();
const isShown = dropdown.classList.toggle('show');
if (isShown) {
loadNotifications();
}
});
// Close dropdown when clicking outside
document.addEventListener('click', function(e) {
if (!dropdown.contains(e.target) && e.target !== bell) {
dropdown.classList.remove('show');
}
});
// Load notifications
function loadNotifications() {
fetch('<?php echo $cfg['main_url']; ?>/f_modules/m_frontend/m_notif/notifications_bell.php', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action=get_notifications'
})
.then(response => response.json())
.then(data => {
renderNotifications(data.notifications);
updateBadge(data.unread_count);
})
.catch(error => {
notificationList.innerHTML = '<div class="loading">Error loading notifications</div>';
});
}
// Render notifications
function renderNotifications(notifications) {
if (notifications.length === 0) {
notificationList.innerHTML = `
<div class="empty-notifications">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"/>
</svg>
<p>No notifications yet</p>
</div>
`;
return;
}
const html = notifications.map(notif => `
<a href="${notif.link || '#'}" class="notification-item ${notif.is_read ? '' : 'unread'}" data-id="${notif.id}">
<div class="notification-icon">
${getNotificationIcon(notif.type)}
</div>
<div class="notification-content">
<div class="notification-message">${notif.message}</div>
<div class="notification-time">${notif.time_ago}</div>
</div>
${notif.is_read ? '' : '<div class="notification-unread-dot"></div>'}
</a>
`).join('');
notificationList.innerHTML = html;
// Add click handlers to mark as read
document.querySelectorAll('.notification-item.unread').forEach(item => {
item.addEventListener('click', function(e) {
markAsRead(this.dataset.id);
});
});
}
// Get icon based on notification type
function getNotificationIcon(type) {
const icons = {
'comment': '💬',
'like': '❤️',
'subscribe': '🔔',
'upload': '📹',
'mention': '@',
'system': ''
};
return icons[type] || '🔔';
}
// Mark as read
function markAsRead(notifId) {
fetch('<?php echo $cfg['main_url']; ?>/f_modules/m_frontend/m_notif/notifications_bell.php', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: `action=mark_read&notif_id=${notifId}`
})
.then(response => response.json())
.then(data => {
updateBadge(data.unread_count);
});
}
// Mark all as read
markAllReadBtn.addEventListener('click', function() {
fetch('<?php echo $cfg['main_url']; ?>/f_modules/m_frontend/m_notif/notifications_bell.php', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action=mark_all_read'
})
.then(response => response.json())
.then(data => {
updateBadge(0);
loadNotifications();
});
});
// Update badge
function updateBadge(count) {
if (count > 0) {
if (badge) {
badge.textContent = count > 99 ? '99+' : count;
badge.style.display = 'block';
}
} else {
if (badge) {
badge.style.display = 'none';
}
}
}
// Poll for new notifications every 30 seconds
setInterval(function() {
fetch('<?php echo $cfg['main_url']; ?>/f_modules/m_frontend/m_notif/notifications_bell.php', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action=get_count'
})
.then(response => response.json())
.then(data => {
updateBadge(data.count);
});
}, 30000);
})();
</script>
<?php endif; ?>