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

View File

@@ -0,0 +1,587 @@
<?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; ?>