google-site-verification: google61fe8ba583a51912.html
Social Media Platform with Content-Based Recommendations

Complete Full-Stack Social Media Application with PHP, MySQL, JavaScript


PROJECT OVERVIEW

A fully functional social media platform with content-based recommendation system that suggests posts, friends, and content based on user interests, interactions, and behavior patterns.

Core Features

User Features

  • ✅ User registration and authentication
  • ✅ Profile creation and customization
  • ✅ Friend requests and connections
  • ✅ News feed with personalized content
  • ✅ Post creation (text, images, videos)
  • ✅ Likes, comments, and shares
  • ✅ Real-time notifications
  • ✅ Direct messaging
  • ✅ Stories (24-hour content)
  • ✅ Saved posts
  • ✅ Block/Report system

Content-Based Recommendations

  • ✅ Personalized news feed algorithm
  • ✅ Friend suggestions based on interests
  • ✅ Content recommendations based on engagement
  • ✅ Trending topics detection
  • ✅ Hashtag-based discovery
  • ✅ Similar content suggestions

Advanced Features

  • ✅ Search with filters
  • ✅ Privacy settings
  • ✅ Activity logs
  • ✅ Analytics dashboard
  • ✅ Admin panel
  • ✅ Moderation tools
  • ✅ Data export

DATABASE SCHEMA (Complete)

CREATE DATABASE social_media_platform; USE social_media_platform; -- Users table (core) CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, email VARCHAR(100) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, full_name VARCHAR(100), bio TEXT, profile_pic VARCHAR(255), cover_photo VARCHAR(255), website VARCHAR(255), location VARCHAR(100), birth_date DATE, gender ENUM('male', 'female', 'other', 'prefer_not_to_say'), phone VARCHAR(20), account_type ENUM('public', 'private') DEFAULT 'public', is_verified BOOLEAN DEFAULT FALSE, is_active BOOLEAN DEFAULT TRUE, is_admin BOOLEAN DEFAULT FALSE, last_login TIMESTAMP NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_email (email), INDEX idx_username (username), INDEX idx_active (is_active), FULLTEXT INDEX ft_search (username, full_name, bio) ); -- User preferences for recommendations CREATE TABLE user_preferences ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, interested_categories JSON, interested_topics JSON, preferred_language VARCHAR(10) DEFAULT 'en', notification_sound BOOLEAN DEFAULT TRUE, dark_mode BOOLEAN DEFAULT FALSE, content_filter_level ENUM('low', 'medium', 'high') DEFAULT 'medium', recommendation_weight FLOAT DEFAULT 1.0, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, UNIQUE KEY unique_user (user_id) ); -- Posts table CREATE TABLE posts ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, content TEXT, media_type ENUM('none', 'image', 'video', 'audio', 'document') DEFAULT 'none', media_url JSON, -- Array of media URLs thumbnail_url VARCHAR(255), visibility ENUM('public', 'friends', 'private', 'custom') DEFAULT 'public', allow_comments BOOLEAN DEFAULT TRUE, allow_shares BOOLEAN DEFAULT TRUE, location VARCHAR(255), tags JSON, -- Array of tagged user IDs hashtags JSON, -- Array of hashtags mentions JSON, -- Array of mentioned user IDs likes_count INT DEFAULT 0, comments_count INT DEFAULT 0, shares_count INT DEFAULT 0, views_count INT DEFAULT 0, is_pinned BOOLEAN DEFAULT FALSE, is_sponsored BOOLEAN DEFAULT FALSE, status ENUM('active', 'deleted', 'reported', 'hidden') DEFAULT 'active', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, INDEX idx_user (user_id), INDEX idx_visibility (visibility), INDEX idx_created (created_at), INDEX idx_trending (likes_count, comments_count, shares_count, created_at), FULLTEXT INDEX ft_content (content) ); -- Post likes CREATE TABLE post_likes ( id INT PRIMARY KEY AUTO_INCREMENT, post_id INT NOT NULL, user_id INT NOT NULL, reaction_type ENUM('like', 'love', 'haha', 'wow', 'sad', 'angry') DEFAULT 'like', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, UNIQUE KEY unique_like (post_id, user_id), INDEX idx_post (post_id) ); -- Comments CREATE TABLE comments ( id INT PRIMARY KEY AUTO_INCREMENT, post_id INT NOT NULL, user_id INT NOT NULL, parent_id INT DEFAULT NULL, content TEXT NOT NULL, media_url VARCHAR(255), likes_count INT DEFAULT 0, is_edited BOOLEAN DEFAULT FALSE, status ENUM('active', 'deleted', 'hidden') DEFAULT 'active', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (parent_id) REFERENCES comments(id) ON DELETE CASCADE, INDEX idx_post (post_id), INDEX idx_user (user_id) ); -- Comment likes CREATE TABLE comment_likes ( id INT PRIMARY KEY AUTO_INCREMENT, comment_id INT NOT NULL, user_id INT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (comment_id) REFERENCES comments(id) ON DELETE CASCADE, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, UNIQUE KEY unique_like (comment_id, user_id) ); -- Shares CREATE TABLE shares ( id INT PRIMARY KEY AUTO_INCREMENT, post_id INT NOT NULL, user_id INT NOT NULL, content TEXT, visibility ENUM('public', 'friends', 'private') DEFAULT 'public', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, INDEX idx_post (post_id) ); -- Friendships CREATE TABLE friendships ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, friend_id INT NOT NULL, status ENUM('pending', 'accepted', 'blocked', 'declined') DEFAULT 'pending', action_user_id INT, -- User who sent the request created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (friend_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (action_user_id) REFERENCES users(id) ON DELETE SET NULL, UNIQUE KEY unique_friendship (user_id, friend_id), INDEX idx_status (status) ); -- Follows (for pages/public figures) CREATE TABLE follows ( id INT PRIMARY KEY AUTO_INCREMENT, follower_id INT NOT NULL, following_id INT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (follower_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (following_id) REFERENCES users(id) ON DELETE CASCADE, UNIQUE KEY unique_follow (follower_id, following_id), INDEX idx_follower (follower_id), INDEX idx_following (following_id) ); -- Messages CREATE TABLE messages ( id INT PRIMARY KEY AUTO_INCREMENT, sender_id INT NOT NULL, receiver_id INT NOT NULL, content TEXT NOT NULL, media_url VARCHAR(255), is_read BOOLEAN DEFAULT FALSE, is_delivered BOOLEAN DEFAULT FALSE, is_deleted BOOLEAN DEFAULT FALSE, replied_to_id INT DEFAULT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (sender_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (receiver_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (replied_to_id) REFERENCES messages(id) ON DELETE SET NULL, INDEX idx_conversation (sender_id, receiver_id), INDEX idx_read (is_read) ); -- Conversations (for grouping messages) CREATE TABLE conversations ( id INT PRIMARY KEY AUTO_INCREMENT, participant1_id INT NOT NULL, participant2_id INT NOT NULL, last_message_id INT, last_message_time TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (participant1_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (participant2_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (last_message_id) REFERENCES messages(id) ON DELETE SET NULL, UNIQUE KEY unique_conversation (participant1_id, participant2_id) ); -- Notifications CREATE TABLE notifications ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, actor_id INT, -- User who performed the action type ENUM('friend_request', 'friend_accept', 'like', 'comment', 'share', 'mention', 'tag', 'message', 'follow', 'birthday') NOT NULL, reference_id INT, -- ID of related content (post, comment, etc.) content TEXT, is_read BOOLEAN DEFAULT FALSE, is_clicked BOOLEAN DEFAULT FALSE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (actor_id) REFERENCES users(id) ON DELETE SET NULL, INDEX idx_user_read (user_id, is_read), INDEX idx_created (created_at) ); -- Stories (temporary content) CREATE TABLE stories ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, media_url VARCHAR(255) NOT NULL, media_type ENUM('image', 'video') NOT NULL, thumbnail_url VARCHAR(255), caption TEXT, viewers_count INT DEFAULT 0, expires_at TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, INDEX idx_user (user_id), INDEX idx_expires (expires_at) ); -- Story views CREATE TABLE story_views ( id INT PRIMARY KEY AUTO_INCREMENT, story_id INT NOT NULL, user_id INT NOT NULL, viewed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (story_id) REFERENCES stories(id) ON DELETE CASCADE, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, UNIQUE KEY unique_view (story_id, user_id) ); -- Hashtags CREATE TABLE hashtags ( id INT PRIMARY KEY AUTO_INCREMENT, tag VARCHAR(100) UNIQUE NOT NULL, posts_count INT DEFAULT 0, last_used TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_tag (tag), INDEX idx_popular (posts_count) ); -- Post hashtags relationship CREATE TABLE post_hashtags ( id INT PRIMARY KEY AUTO_INCREMENT, post_id INT NOT NULL, hashtag_id INT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE, FOREIGN KEY (hashtag_id) REFERENCES hashtags(id) ON DELETE CASCADE, UNIQUE KEY unique_post_hashtag (post_id, hashtag_id) ); -- User interests (for recommendations) CREATE TABLE user_interests ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, interest VARCHAR(100) NOT NULL, weight FLOAT DEFAULT 1.0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, UNIQUE KEY unique_interest (user_id, interest), INDEX idx_user (user_id) ); -- User activity log (for recommendations) CREATE TABLE user_activities ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, activity_type ENUM('post_view', 'post_like', 'post_comment', 'post_share', 'profile_view', 'search', 'click') NOT NULL, target_id INT, -- ID of target content target_type VARCHAR(50), -- 'post', 'user', 'hashtag' metadata JSON, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, INDEX idx_user_activity (user_id, activity_type), INDEX idx_created (created_at), INDEX idx_target (target_id, target_type) ); -- Content recommendations cache CREATE TABLE content_recommendations ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, post_id INT NOT NULL, score DECIMAL(5,4), reason JSON, -- Reasons for recommendation is_viewed BOOLEAN DEFAULT FALSE, is_clicked BOOLEAN DEFAULT FALSE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, expires_at TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE, INDEX idx_user_score (user_id, score), INDEX idx_expires (expires_at) ); -- Friend recommendations cache CREATE TABLE friend_recommendations ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, suggested_user_id INT NOT NULL, score DECIMAL(5,4), reason JSON, is_viewed BOOLEAN DEFAULT FALSE, is_connected BOOLEAN DEFAULT FALSE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, expires_at TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (suggested_user_id) REFERENCES users(id) ON DELETE CASCADE, INDEX idx_user_score (user_id, score) ); -- Blocks CREATE TABLE blocks ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, blocked_user_id INT NOT NULL, reason VARCHAR(255), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (blocked_user_id) REFERENCES users(id) ON DELETE CASCADE, UNIQUE KEY unique_block (user_id, blocked_user_id) ); -- Reports CREATE TABLE reports ( id INT PRIMARY KEY AUTO_INCREMENT, reporter_id INT NOT NULL, reported_type ENUM('user', 'post', 'comment', 'message') NOT NULL, reported_id INT NOT NULL, reason VARCHAR(255), description TEXT, status ENUM('pending', 'reviewed', 'resolved', 'dismissed') DEFAULT 'pending', reviewed_by INT, reviewed_at TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (reporter_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (reviewed_by) REFERENCES users(id) ON DELETE SET NULL, INDEX idx_status (status), INDEX idx_reported (reported_type, reported_id) ); -- User sessions CREATE TABLE user_sessions ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, session_token VARCHAR(255) UNIQUE NOT NULL, device_info JSON, ip_address VARCHAR(45), user_agent TEXT, last_activity TIMESTAMP, expires_at TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, INDEX idx_token (session_token), INDEX idx_user (user_id) ); -- Settings CREATE TABLE settings ( id INT PRIMARY KEY AUTO_INCREMENT, setting_key VARCHAR(100) UNIQUE NOT NULL, setting_value TEXT, description TEXT, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); -- Insert default settings INSERT INTO settings (setting_key, setting_value, description) VALUES ('site_name', 'SocialConnect', 'Website name'), ('site_description', 'Connect with friends and the world around you', 'Site description'), ('max_upload_size', '100', 'Maximum upload size in MB'), ('allowed_file_types', 'jpg,jpeg,png,gif,mp4,mp3,pdf,doc,docx', 'Allowed file types'), ('posts_per_page', '20', 'Number of posts per page'), ('recommendation_limit', '50', 'Number of recommendations to generate'), ('friend_suggestion_limit', '20', 'Number of friend suggestions'), ('trending_days', '7', 'Days to consider for trending'), ('maintenance_mode', '0', 'Maintenance mode status'), ('registration_enabled', '1', 'Allow new registrations');

BACKEND IMPLEMENTATION

config.php

<?php session_start(); // Database configuration define('DB_HOST', 'localhost'); define('DB_NAME', 'social_media_platform'); define('DB_USER', 'root'); define('DB_PASS', ''); // Site configuration define('SITE_URL', 'http://localhost/social-media'); define('SITE_NAME', 'SocialConnect'); define('UPLOAD_DIR', __DIR__ . '/uploads/'); define('MAX_FILE_SIZE', 100 * 1024 * 1024); // 100MB define('TIMEZONE', 'UTC'); define('DEBUG_MODE', true); // Set timezone date_default_timezone_set(TIMEZONE); // Error reporting if (DEBUG_MODE) { error_reporting(E_ALL); ini_set('display_errors', 1); } else { error_reporting(0); ini_set('display_errors', 0); } // Database connection try { $pdo = new PDO( "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4", DB_USER, DB_PASS, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false ] ); } catch(PDOException $e) { die("Connection failed: " . $e->getMessage()); } // Create upload directories if they don't exist $upload_dirs = [ UPLOAD_DIR, UPLOAD_DIR . 'profiles/', UPLOAD_DIR . 'covers/', UPLOAD_DIR . 'posts/', UPLOAD_DIR . 'stories/', UPLOAD_DIR . 'messages/' ]; foreach ($upload_dirs as $dir) { if (!file_exists($dir)) { mkdir($dir, 0777, true); } } // Include helper functions require_once 'helpers.php'; // Autoload classes spl_autoload_register(function ($class) { $file = __DIR__ . '/classes/' . $class . '.php'; if (file_exists($file)) { require_once $file; } }); // Check if user is logged in function isLoggedIn() { return isset($_SESSION['user_id']); } // Get current user ID function getCurrentUserId() { return $_SESSION['user_id'] ?? null; } // Get current user data function getCurrentUser() { global $pdo; if (!isLoggedIn()) return null; static $user = null; if ($user === null) { $stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?"); $stmt->execute([$_SESSION['user_id']]); $user = $stmt->fetch(); } return $user; } // Redirect function function redirect($url) { header("Location: " . SITE_URL . $url); exit; } // JSON response function function jsonResponse($data, $status = 200) { http_response_code($status); header('Content-Type: application/json'); echo json_encode($data); exit; } ?>

helpers.php

<?php // Helper functions // Sanitize input function sanitize($input) { return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8'); } // Generate random string function generateRandomString($length = 32) { return bin2hex(random_bytes($length / 2)); } // Format date function formatDate($date, $format = 'M j, Y') { return date($format, strtotime($date)); } // Time ago function function timeAgo($datetime) { $time = strtotime($datetime); $now = time(); $diff = $now - $time; if ($diff < 60) { return $diff . ' seconds ago'; } 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 < 2592000) { $days = floor($diff / 86400); return $days . ' day' . ($days > 1 ? 's' : '') . ' ago'; } elseif ($diff < 31536000) { $months = floor($diff / 2592000); return $months . ' month' . ($months > 1 ? 's' : '') . ' ago'; } else { $years = floor($diff / 31536000); return $years . ' year' . ($years > 1 ? 's' : '') . ' ago'; } } // Upload file function uploadFile($file, $folder, $allowedTypes = ['jpg', 'jpeg', 'png', 'gif', 'mp4', 'mp3']) { if ($file['error'] !== UPLOAD_ERR_OK) { return ['success' => false, 'message' => 'Upload failed']; } $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); if (!in_array($extension, $allowedTypes)) { return ['success' => false, 'message' => 'File type not allowed']; } if ($file['size'] > MAX_FILE_SIZE) { return ['success' => false, 'message' => 'File too large']; } $filename = uniqid() . '_' . time() . '.' . $extension; $filepath = UPLOAD_DIR . $folder . '/' . $filename; if (move_uploaded_file($file['tmp_name'], $filepath)) { return [ 'success' => true, 'filename' => $filename, 'path' => 'uploads/' . $folder . '/' . $filename ]; } return ['success' => false, 'message' => 'Failed to save file']; } // Get file URL function getFileUrl($path) { if (empty($path)) return null; return SITE_URL . '/' . $path; } // Get profile picture URL function getProfilePicUrl($user) { if (!empty($user['profile_pic'])) { return getFileUrl($user['profile_pic']); } return SITE_URL . '/assets/images/default-avatar.png'; } // Get cover photo URL function getCoverUrl($user) { if (!empty($user['cover_photo'])) { return getFileUrl($user['cover_photo']); } return SITE_URL . '/assets/images/default-cover.jpg'; } // Create notification function createNotification($userId, $actorId, $type, $referenceId = null, $content = null) { global $pdo; $stmt = $pdo->prepare(" INSERT INTO notifications (user_id, actor_id, type, reference_id, content) VALUES (?, ?, ?, ?, ?) "); return $stmt->execute([$userId, $actorId, $type, $referenceId, $content]); } // Extract hashtags from content function extractHashtags($content) { preg_match_all('/#(\w+)/', $content, $matches); return $matches[1]; } // Extract mentions from content function extractMentions($content) { preg_match_all('/@(\w+)/', $content, $matches); return $matches[1]; } // Process hashtags function processHashtags($postId, $hashtags) { global $pdo; foreach ($hashtags as $tag) { // Insert or update hashtag $stmt = $pdo->prepare(" INSERT INTO hashtags (tag, posts_count, last_used) VALUES (?, 1, NOW()) ON DUPLICATE KEY UPDATE posts_count = posts_count + 1, last_used = NOW() "); $stmt->execute([$tag]); // Get hashtag id $stmt = $pdo->prepare("SELECT id FROM hashtags WHERE tag = ?"); $stmt->execute([$tag]); $hashtag = $stmt->fetch(); // Link post to hashtag $stmt = $pdo->prepare(" INSERT IGNORE INTO post_hashtags (post_id, hashtag_id) VALUES (?, ?) "); $stmt->execute([$postId, $hashtag['id']]); } } // Get friendship status function getFriendshipStatus($userId, $friendId) { global $pdo; $stmt = $pdo->prepare(" SELECT status FROM friendships WHERE (user_id = ? AND friend_id = ?) OR (user_id = ? AND friend_id = ?) "); $stmt->execute([$userId, $friendId, $friendId, $userId]); $result = $stmt->fetch(); if ($result) { return $result['status']; } return 'none'; } // Check if user is followed function isFollowing($followerId, $followingId) { global $pdo; $stmt = $pdo->prepare("SELECT id FROM follows WHERE follower_id = ? AND following_id = ?"); $stmt->execute([$followerId, $followingId]); return $stmt->fetch() ? true : false; } // Get mutual friends count function getMutualFriendsCount($userId, $otherUserId) { global $pdo; $stmt = $pdo->prepare(" SELECT COUNT(*) as mutual_count FROM friendships f1 JOIN friendships f2 ON f1.friend_id = f2.friend_id WHERE f1.user_id = ? AND f1.status = 'accepted' AND f2.user_id = ? AND f2.status = 'accepted' AND f1.friend_id != ? AND f2.friend_id != ? "); $stmt->execute([$userId, $otherUserId, $userId, $otherUserId]); $result = $stmt->fetch(); return $result['mutual_count']; } ?>

classes/User.php

<?php class User { private $pdo; public function __construct($pdo) { $this->pdo = $pdo; } // Register new user public function register($data) { // Check if user exists $stmt = $this->pdo->prepare("SELECT id FROM users WHERE username = ? OR email = ?"); $stmt->execute([$data['username'], $data['email']]); if ($stmt->fetch()) { return ['success' => false, 'message' => 'Username or email already exists']; } // Hash password $passwordHash = password_hash($data['password'], PASSWORD_DEFAULT); // Insert user $stmt = $this->pdo->prepare(" INSERT INTO users (username, email, password_hash, full_name, birth_date, gender) VALUES (?, ?, ?, ?, ?, ?) "); if ($stmt->execute([ $data['username'], $data['email'], $passwordHash, $data['full_name'] ?? null, $data['birth_date'] ?? null, $data['gender'] ?? null ])) { $userId = $this->pdo->lastInsertId(); // Create user preferences $prefStmt = $this->pdo->prepare(" INSERT INTO user_preferences (user_id) VALUES (?) "); $prefStmt->execute([$userId]); // Create session $this->createSession($userId); return ['success' => true, 'user_id' => $userId]; } return ['success' => false, 'message' => 'Registration failed']; } // Login user public function login($username, $password) { $stmt = $this->pdo->prepare("SELECT * FROM users WHERE username = ? OR email = ?"); $stmt->execute([$username, $username]); $user = $stmt->fetch(); if ($user && password_verify($password, $user['password_hash'])) { if (!$user['is_active']) { return ['success' => false, 'message' => 'Account is deactivated']; } // Update last login $updateStmt = $this->pdo->prepare("UPDATE users SET last_login = NOW() WHERE id = ?"); $updateStmt->execute([$user['id']]); // Create session $this->createSession($user['id']); return ['success' => true, 'user' => $user]; } return ['success' => false, 'message' => 'Invalid credentials']; } // Create user session private function createSession($userId) { $_SESSION['user_id'] = $userId; // Create database session $token = generateRandomString(64); $expires = date('Y-m-d H:i:s', strtotime('+30 days')); $stmt = $this->pdo->prepare(" INSERT INTO user_sessions (user_id, session_token, ip_address, user_agent, expires_at) VALUES (?, ?, ?, ?, ?) "); $stmt->execute([ $userId, $token, $_SERVER['REMOTE_ADDR'] ?? null, $_SERVER['HTTP_USER_AGENT'] ?? null, $expires ]); setcookie('session_token', $token, strtotime('+30 days'), '/', '', true, true); } // Get user by ID public function getById($userId) { $stmt = $this->pdo->prepare(" SELECT u.*, (SELECT COUNT(*) FROM friendships WHERE (user_id = u.id OR friend_id = u.id) AND status = 'accepted') as friends_count, (SELECT COUNT(*) FROM posts WHERE user_id = u.id AND status = 'active') as posts_count, (SELECT COUNT(*) FROM follows WHERE following_id = u.id) as followers_count, (SELECT COUNT(*) FROM follows WHERE follower_id = u.id) as following_count FROM users u WHERE u.id = ? AND u.is_active = 1 "); $stmt->execute([$userId]); return $stmt->fetch(); } // Get user by username public function getByUsername($username) { $stmt = $this->pdo->prepare("SELECT * FROM users WHERE username = ? AND is_active = 1"); $stmt->execute([$username]); return $stmt->fetch(); } // Update profile public function updateProfile($userId, $data) { $allowed = ['full_name', 'bio', 'website', 'location', 'phone', 'account_type']; $updates = []; $params = []; foreach ($data as $key => $value) { if (in_array($key, $allowed)) { $updates[] = "$key = ?"; $params[] = $value; } } if (empty($updates)) { return false; } $params[] = $userId; $sql = "UPDATE users SET " . implode(', ', $updates) . " WHERE id = ?"; $stmt = $this->pdo->prepare($sql); return $stmt->execute($params); } // Update profile picture public function updateProfilePic($userId, $file) { $result = uploadFile($file, 'profiles', ['jpg', 'jpeg', 'png', 'gif']); if ($result['success']) { $stmt = $this->pdo->prepare("UPDATE users SET profile_pic = ? WHERE id = ?"); return $stmt->execute([$result['path'], $userId]); } return false; } // Update cover photo public function updateCoverPhoto($userId, $file) { $result = uploadFile($file, 'covers', ['jpg', 'jpeg', 'png', 'gif']); if ($result['success']) { $stmt = $this->pdo->prepare("UPDATE users SET cover_photo = ? WHERE id = ?"); return $stmt->execute([$result['path'], $userId]); } return false; } // Search users public function search($query, $limit = 20, $offset = 0) { $stmt = $this->pdo->prepare(" SELECT id, username, full_name, profile_pic, bio FROM users WHERE (username LIKE ? OR full_name LIKE ? OR bio LIKE ?) AND is_active = 1 ORDER BY CASE WHEN username = ? THEN 1 WHEN full_name = ? THEN 2 WHEN username LIKE ? THEN 3 WHEN full_name LIKE ? THEN 4 ELSE 5 END LIMIT ? OFFSET ? "); $searchTerm = "%$query%"; $exactTerm = $query; $startTerm = "$query%"; $stmt->execute([ $searchTerm, $searchTerm, $searchTerm, $exactTerm, $exactTerm, $startTerm, $startTerm, $limit, $offset ]); return $stmt->fetchAll(); } // Get followers public function getFollowers($userId, $limit = 20) { $stmt = $this->pdo->prepare(" SELECT u.* FROM follows f JOIN users u ON f.follower_id = u.id WHERE f.following_id = ? AND u.is_active = 1 ORDER BY f.created_at DESC LIMIT ? "); $stmt->execute([$userId, $limit]); return $stmt->fetchAll(); } // Get following public function getFollowing($userId, $limit = 20) { $stmt = $this->pdo->prepare(" SELECT u.* FROM follows f JOIN users u ON f.following_id = u.id WHERE f.follower_id = ? AND u.is_active = 1 ORDER BY f.created_at DESC LIMIT ? "); $stmt->execute([$userId, $limit]); return $stmt->fetchAll(); } // Get friends public function getFriends($userId, $limit = 20) { $stmt = $this->pdo->prepare(" SELECT u.*, (SELECT COUNT(*) FROM messages WHERE (sender_id = ? AND receiver_id = u.id) OR (sender_id = u.id AND receiver_id = ?)) as messages_count FROM friendships f JOIN users u ON (f.user_id = u.id OR f.friend_id = u.id) WHERE (f.user_id = ? OR f.friend_id = ?) AND f.status = 'accepted' AND u.id != ? AND u.is_active = 1 ORDER BY messages_count DESC LIMIT ? "); $stmt->execute([$userId, $userId, $userId, $userId, $userId, $limit]); return $stmt->fetchAll(); } } ?>

classes/Post.php

<?php class Post { private $pdo; public function __construct($pdo) { $this->pdo = $pdo; } // Create new post public function create($userId, $data, $files = null) { // Handle media upload $mediaUrls = []; $mediaType = 'none'; if ($files && isset($files['media'])) { foreach ($files['media']['tmp_name'] as $key => $tmp_name) { if ($files['media']['error'][$key] == 0) { $file = [ 'name' => $files['media']['name'][$key], 'type' => $files['media']['type'][$key], 'tmp_name' => $tmp_name, 'error' => $files['media']['error'][$key], 'size' => $files['media']['size'][$key] ]; $result = uploadFile($file, 'posts', ['jpg', 'jpeg', 'png', 'gif', 'mp4', 'mp3']); if ($result['success']) { $mediaUrls[] = $result['path']; $mediaType = $this->getMediaType($file['type']); } } } } // Process hashtags $hashtags = extractHashtags($data['content']); // Process mentions $mentions = extractMentions($data['content']); $mentionedUsers = $this->getUserIdsFromUsernames($mentions); // Insert post $stmt = $this->pdo->prepare(" INSERT INTO posts (user_id, content, media_type, media_url, visibility, allow_comments, allow_shares, location, hashtags, mentions) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) "); $success = $stmt->execute([ $userId, $data['content'], $mediaType, json_encode($mediaUrls), $data['visibility'] ?? 'public', $data['allow_comments'] ?? 1, $data['allow_shares'] ?? 1, $data['location'] ?? null, json_encode($hashtags), json_encode($mentionedUsers) ]); if ($success) { $postId = $this->pdo->lastInsertId(); // Process hashtags if (!empty($hashtags)) { processHashtags($postId, $hashtags); } // Create notifications for mentions foreach ($mentionedUsers as $mentionedUserId) { if ($mentionedUserId != $userId) { createNotification($mentionedUserId, $userId, 'mention', $postId); } } return ['success' => true, 'post_id' => $postId]; } return ['success' => false, 'message' => 'Failed to create post']; } // Get post by ID public function getById($postId, $userId = null) { $stmt = $this->pdo->prepare(" SELECT p.*, u.username, u.full_name, u.profile_pic, (SELECT COUNT(*) FROM post_likes WHERE post_id = p.id) as likes_count, (SELECT COUNT(*) FROM comments WHERE post_id = p.id AND status = 'active') as comments_count, (SELECT COUNT(*) FROM shares WHERE post_id = p.id) as shares_count, ? IN (SELECT user_id FROM post_likes WHERE post_id = p.id) as user_liked, ? IN (SELECT user_id FROM post_likes WHERE post_id = p.id AND reaction_type = 'like') as user_liked_type, (SELECT reaction_type FROM post_likes WHERE post_id = p.id AND user_id = ?) as user_reaction FROM posts p JOIN users u ON p.user_id = u.id WHERE p.id = ? AND p.status = 'active' "); $stmt->execute([$userId, $userId, $userId, $postId]); $post = $stmt->fetch(); if ($post) { $post['media_url'] = json_decode($post['media_url'], true); $post['hashtags'] = json_decode($post['hashtags'], true); $post['mentions'] = json_decode($post['mentions'], true); // Check privacy if (!$this->canViewPost($post, $userId)) { return null; } // Increment views $this->incrementViews($postId); } return $post; } // Check if user can view post private function canViewPost($post, $userId) { if ($post['visibility'] == 'public') { return true; } if ($post['visibility'] == 'friends') { $status = getFriendshipStatus($userId, $post['user_id']); return $status == 'accepted'; } if ($post['visibility'] == 'private') { return $userId == $post['user_id']; } return false; } // Get feed posts public function getFeed($userId, $limit = 20, $offset = 0) { // Get friends and following $friends = $this->getFriendIds($userId); $following = $this->getFollowingIds($userId); $userIds = array_merge([$userId], $friends, $following); $placeholders = implode(',', array_fill(0, count($userIds), '?')); $stmt = $this->pdo->prepare(" SELECT p.*, u.username, u.full_name, u.profile_pic, (SELECT COUNT(*) FROM post_likes WHERE post_id = p.id) as likes_count, (SELECT COUNT(*) FROM comments WHERE post_id = p.id AND status = 'active') as comments_count, ? IN (SELECT user_id FROM post_likes WHERE post_id = p.id) as user_liked FROM posts p JOIN users u ON p.user_id = u.id WHERE p.user_id IN ($placeholders) AND p.status = 'active' AND p.visibility IN ('public', 'friends') ORDER BY p.created_at DESC LIMIT ? OFFSET ? "); $params = array_merge([$userId], $userIds, [$limit, $offset]); $stmt->execute($params); return $stmt->fetchAll(); } // Like/Unlike post public function like($postId, $userId, $reaction = 'like') { // Check if already liked $stmt = $this->pdo->prepare("SELECT id, reaction_type FROM post_likes WHERE post_id = ? AND user_id = ?"); $stmt->execute([$postId, $userId]); $existing = $stmt->fetch(); if ($existing) { if ($existing['reaction_type'] == $reaction) { // Remove like $stmt = $this->pdo->prepare("DELETE FROM post_likes WHERE post_id = ? AND user_id = ?"); $stmt->execute([$postId, $userId]); $this->updateLikeCount($postId); return ['action' => 'removed']; } else { // Update reaction $stmt = $this->pdo->prepare("UPDATE post_likes SET reaction_type = ? WHERE post_id = ? AND user_id = ?"); $stmt->execute([$reaction, $postId, $userId]); $this->updateLikeCount($postId); return ['action' => 'updated', 'reaction' => $reaction]; } } else { // Add like $stmt = $this->pdo->prepare("INSERT INTO post_likes (post_id, user_id, reaction_type) VALUES (?, ?, ?)"); $stmt->execute([$postId, $userId, $reaction]); $this->updateLikeCount($postId); // Create notification $post = $this->getById($postId); if ($post && $post['user_id'] != $userId) { createNotification($post['user_id'], $userId, 'like', $postId); } return ['action' => 'added', 'reaction' => $reaction]; } } // Update like count private function updateLikeCount($postId) { $stmt = $this->pdo->prepare(" UPDATE posts SET likes_count = (SELECT COUNT(*) FROM post_likes WHERE post_id = ?) WHERE id = ? "); $stmt->execute([$postId, $postId]); } // Increment views private function incrementViews($postId) { $stmt = $this->pdo->prepare("UPDATE posts SET views_count = views_count + 1 WHERE id = ?"); $stmt->execute([$postId]); } // Add comment public function addComment($postId, $userId, $content, $parentId = null) { $stmt = $this->pdo->prepare(" INSERT INTO comments (post_id, user_id, parent_id, content) VALUES (?, ?, ?, ?) "); if ($stmt->execute([$postId, $userId, $parentId, $content])) { $commentId = $this->pdo->lastInsertId(); // Update comment count $this->updateCommentCount($postId); // Create notification $post = $this->getById($postId); if ($post && $post['user_id'] != $userId) { createNotification($post['user_id'], $userId, 'comment', $postId); } return $this->getComment($commentId); } return null; } // Get comments for post public function getComments($postId) { $stmt = $this->pdo->prepare(" SELECT c.*, u.username, u.full_name, u.profile_pic, (SELECT COUNT(*) FROM comment_likes WHERE comment_id = c.id) as likes_count FROM comments c JOIN users u ON c.user_id = u.id WHERE c.post_id = ? AND c.parent_id IS NULL AND c.status = 'active' ORDER BY c.created_at DESC "); $stmt->execute([$postId]); $comments = $stmt->fetchAll(); foreach ($comments as &$comment) { $comment['replies'] = $this->getCommentReplies($comment['id']); } return $comments; } // Get comment replies private function getCommentReplies($commentId) { $stmt = $this->pdo->prepare(" SELECT c.*, u.username, u.full_name, u.profile_pic, (SELECT COUNT(*) FROM comment_likes WHERE comment_id = c.id) as likes_count FROM comments c JOIN users u ON c.user_id = u.id WHERE c.parent_id = ? AND c.status = 'active' ORDER BY c.created_at ASC "); $stmt->execute([$commentId]); return $stmt->fetchAll(); } // Get single comment private function getComment($commentId) { $stmt = $this->pdo->prepare(" SELECT c.*, u.username, u.full_name, u.profile_pic, (SELECT COUNT(*) FROM comment_likes WHERE comment_id = c.id) as likes_count FROM comments c JOIN users u ON c.user_id = u.id WHERE c.id = ? "); $stmt->execute([$commentId]); return $stmt->fetch(); } // Update comment count private function updateCommentCount($postId) { $stmt = $this->pdo->prepare(" UPDATE posts SET comments_count = (SELECT COUNT(*) FROM comments WHERE post_id = ? AND status = 'active') WHERE id = ? "); $stmt->execute([$postId, $postId]); } // Share post public function share($postId, $userId, $content = null, $visibility = 'public') { $stmt = $this->pdo->prepare(" INSERT INTO shares (post_id, user_id, content, visibility) VALUES (?, ?, ?, ?) "); if ($stmt->execute([$postId, $userId, $content, $visibility])) { // Update share count $this->updateShareCount($postId); // Create notification $post = $this->getById($postId); if ($post && $post['user_id'] != $userId) { createNotification($post['user_id'], $userId, 'share', $postId); } return true; } return false; } // Update share count private function updateShareCount($postId) { $stmt = $this->pdo->prepare(" UPDATE posts SET shares_count = (SELECT COUNT(*) FROM shares WHERE post_id = ?) WHERE id = ? "); $stmt->execute([$postId, $postId]); } // Get friend IDs private function getFriendIds($userId) { $stmt = $this->pdo->prepare(" SELECT CASE WHEN user_id = ? THEN friend_id ELSE user_id END as friend_id FROM friendships WHERE (user_id = ? OR friend_id = ?) AND status = 'accepted' "); $stmt->execute([$userId, $userId, $userId]); return $stmt->fetchAll(PDO::FETCH_COLUMN); } // Get following IDs private function getFollowingIds($userId) { $stmt = $this->pdo->prepare(" SELECT following_id FROM follows WHERE follower_id = ? "); $stmt->execute([$userId]); return $stmt->fetchAll(PDO::FETCH_COLUMN); } // Get user IDs from usernames private function getUserIdsFromUsernames($usernames) { if (empty($usernames)) { return []; } $placeholders = implode(',', array_fill(0, count($usernames), '?')); $stmt = $this->pdo->prepare("SELECT id FROM users WHERE username IN ($placeholders)"); $stmt->execute($usernames); return $stmt->fetchAll(PDO::FETCH_COLUMN); } // Get media type from mime private function getMediaType($mime) { if (strpos($mime, 'image') !== false) return 'image'; if (strpos($mime, 'video') !== false) return 'video'; if (strpos($mime, 'audio') !== false) return 'audio'; return 'document'; } } ?>

classes/SocialRecommender.php (Core Recommendation Algorithm)

<?php class SocialRecommender { private $pdo; public function __construct($pdo) { $this->pdo = $pdo; } // Main recommendation engine public function getRecommendations($userId, $type = 'feed', $limit = 20) { switch ($type) { case 'feed': return $this->getFeedRecommendations($userId, $limit); case 'friends': return $this->getFriendRecommendations($userId, $limit); case 'content': return $this->getContentRecommendations($userId, $limit); case 'trending': return $this->getTrendingContent($limit); default: return []; } } // Feed recommendations (personalized posts) private function getFeedRecommendations($userId, $limit) { // Check cache $cached = $this->getCachedRecommendations($userId, 'feed', $limit); if ($cached) { return $cached; } // Get user interests and behavior $userProfile = $this->buildUserProfile($userId); // Get candidate posts $candidates = $this->getCandidatePosts($userId); // Score each post $recommendations = []; foreach ($candidates as $post) { $score = $this->calculatePostScore($post, $userProfile); if ($score > 0.3) { $recommendations[] = [ 'post' => $post, 'score' => $score, 'reasons' => $this->getRecommendationReasons($post, $userProfile) ]; } } // Sort by score usort($recommendations, function($a, $b) { return $b['score'] <=> $a['score']; }); $recommendations = array_slice($recommendations, 0, $limit); // Cache results $this->cacheRecommendations($userId, 'feed', $recommendations); return $recommendations; } // Friend recommendations private function getFriendRecommendations($userId, $limit) { // Check cache $cached = $this->getCachedRecommendations($userId, 'friends', $limit); if ($cached) { return $cached; } // Get existing friends $existingFriends = $this->getExistingFriendIds($userId); // Get mutual friends and interests $candidates = $this->getFriendCandidates($userId, $existingFriends); // Score each candidate $recommendations = []; foreach ($candidates as $candidate) { $score = $this->calculateFriendScore($userId, $candidate); if ($score > 0.4) { $recommendations[] = [ 'user' => $candidate, 'score' => $score, 'reasons' => $this->getFriendReasons($userId, $candidate) ]; } } // Sort by score usort($recommendations, function($a, $b) { return $b['score'] <=> $a['score']; }); $recommendations = array_slice($recommendations, 0, $limit); // Cache results $this->cacheRecommendations($userId, 'friends', $recommendations); return $recommendations; } // Build user profile from activities private function buildUserProfile($userId) { $profile = [ 'interests' => [], 'categories' => [], 'hashtags' => [], 'active_hours' => [], 'engagement_score' => 0, 'content_preferences' => [] ]; // Get user interests from profile $stmt = $this->pdo->prepare("SELECT interested_topics FROM user_preferences WHERE user_id = ?"); $stmt->execute([$userId]); $prefs = $stmt->fetch(); if ($prefs && $prefs['interested_topics']) { $profile['interests'] = json_decode($prefs['interested_topics'], true) ?? []; } // Analyze user activities (last 30 days) $stmt = $this->pdo->prepare(" SELECT activity_type, target_type, metadata, COUNT(*) as activity_count, HOUR(created_at) as hour FROM user_activities WHERE user_id = ? AND created_at > DATE_SUB(NOW(), INTERVAL 30 DAY) GROUP BY activity_type, target_type, HOUR(created_at) ORDER BY activity_count DESC "); $stmt->execute([$userId]); $activities = $stmt->fetchAll(); foreach ($activities as $activity) { // Track active hours $profile['active_hours'][$activity['hour']] = ($profile['active_hours'][$activity['hour']] ?? 0) + $activity['activity_count']; // Track content preferences if ($activity['target_type'] == 'post') { $metadata = json_decode($activity['metadata'], true); if (isset($metadata['category'])) { $profile['categories'][$metadata['category']] = ($profile['categories'][$metadata['category']] ?? 0) + $activity['activity_count']; } if (isset($metadata['hashtags'])) { foreach ($metadata['hashtags'] as $tag) { $profile['hashtags'][$tag] = ($profile['hashtags'][$tag] ?? 0) + $activity['activity_count']; } } } } // Calculate engagement score $totalActivities = array_sum($activities); $profile['engagement_score'] = min(1, $totalActivities / 100); return $profile; } // Calculate post relevance score private function calculatePostScore($post, $userProfile) { $weights = [ 'interest_match' => 0.35, 'hashtag_match' => 0.25, 'recency' => 0.15, 'popularity' => 0.15, 'engagement' => 0.10 ]; $scores = [ 'interest_match' => $this->calculateInterestMatch($post, $userProfile), 'hashtag_match' => $this->calculateHashtagMatch($post, $userProfile), 'recency' => $this->calculateRecencyScore($post['created_at']), 'popularity' => $this->calculatePopularityScore($post), 'engagement' => $this->calculateEngagementScore($post, $userProfile) ]; $total = 0; foreach ($weights as $factor => $weight) { $total += $scores[$factor] * $weight; } return $total; } // Interest match score private function calculateInterestMatch($post, $userProfile) { if (empty($userProfile['interests'])) { return 0.5; } $postContent = strtolower($post['content'] ?? ''); $matchCount = 0; foreach ($userProfile['interests'] as $interest) { if (strpos($postContent, strtolower($interest)) !== false) { $matchCount++; } } return min(1, $matchCount / count($userProfile['interests'])); } // Hashtag match score private function calculateHashtagMatch($post, $userProfile) { $postHashtags = json_decode($post['hashtags'], true) ?? []; if (empty($postHashtags) || empty($userProfile['hashtags'])) { return 0; } $matchCount = 0; foreach ($postHashtags as $tag) { if (isset($userProfile['hashtags'][$tag])) { $matchCount += $userProfile['hashtags'][$tag]; } } return min(1, $matchCount / count($postHashtags)); } // Recency score (newer posts get higher scores) private function calculateRecencyScore($createdAt) { $hours = (time() - strtotime($createdAt)) / 3600; return max(0, 1 - ($hours / 72)); // Decay over 3 days } // Popularity score private function calculatePopularityScore($post) { $likes = $post['likes_count'] ?? 0; $comments = $post['comments_count'] ?? 0; $shares = $post['shares_count'] ?? 0; $score = ($likes * 1 + $comments * 2 + $shares * 3) / 100; return min(1, $score); } // Engagement score (based on user's past behavior) private function calculateEngagementScore($post, $userProfile) { // Check if user has engaged with similar content return $userProfile['engagement_score'] ?? 0.5; } // Calculate friend recommendation score private function calculateFriendScore($userId, $candidate) { $weights = [ 'mutual_friends' => 0.4, 'common_interests' => 0.3, 'interaction_score' => 0.2, 'location_score' => 0.1 ]; $scores = [ 'mutual_friends' => $this->calculateMutualFriendsScore($userId, $candidate['id']), 'common_interests' => $this->calculateCommonInterestsScore($userId, $candidate['id']), 'interaction_score' => $this->calculateInteractionScore($userId, $candidate['id']), 'location_score' => $this->calculateLocationScore($userId, $candidate) ]; $total = 0; foreach ($weights as $factor => $weight) { $total += $scores[$factor] * $weight; } return $total; } // Mutual friends score private function calculateMutualFriendsScore($userId, $candidateId) { $mutualCount = getMutualFriendsCount($userId, $candidateId); return min(1, $mutualCount / 10); // Cap at 10 mutual friends } // Common interests score private function calculateCommonInterestsScore($userId, $candidateId) { $stmt = $this->pdo->prepare(" SELECT interest FROM user_interests WHERE user_id = ? "); $stmt->execute([$userId]); $userInterests = $stmt->fetchAll(PDO::FETCH_COLUMN); $stmt->execute([$candidateId]); $candidateInterests = $stmt->fetchAll(PDO::FETCH_COLUMN); $common = array_intersect($userInterests, $candidateInterests); return count($common) / max(1, count($userInterests)); } // Interaction score (how often they interact) private function calculateInteractionScore($userId, $candidateId) { $stmt = $this->pdo->prepare(" SELECT COUNT(*) as interactions FROM user_activities WHERE user_id = ? AND target_id = ? AND target_type = 'user' OR user_id = ? AND target_id = ? AND target_type = 'user' "); $stmt->execute([$userId, $candidateId, $candidateId, $userId]); $result = $stmt->fetch(); return min(1, ($result['interactions'] ?? 0) / 20); } // Location score private function calculateLocationScore($userId, $candidate) { // Get user location $stmt = $this->pdo->prepare("SELECT location FROM users WHERE id = ?"); $stmt->execute([$userId]); $user = $stmt->fetch(); if (empty($user['location']) || empty($candidate['location'])) { return 0.5; } return $user['location'] == $candidate['location'] ? 1 : 0.3; } // Get friend candidates private function getFriendCandidates($userId, $existingFriends) { $excludeIds = array_merge([$userId], $existingFriends); $placeholders = implode(',', array_fill(0, count($excludeIds), '?')); $stmt = $this->pdo->prepare(" SELECT u.* FROM users u WHERE u.id NOT IN ($placeholders) AND u.is_active = 1 ORDER BY RAND() LIMIT 100 "); $stmt->execute($excludeIds); return $stmt->fetchAll(); } // Get candidate posts for feed private function getCandidatePosts($userId) { // Get friends and followed users $friendIds = $this->getFriendIds($userId); $followingIds = $this->getFollowingIds($userId); $userIds = array_merge($friendIds, $followingIds); if (empty($userIds)) { return []; } $placeholders = implode(',', array_fill(0, count($userIds), '?')); $stmt = $this->pdo->prepare(" SELECT p.*, u.username, u.full_name, u.profile_pic FROM posts p JOIN users u ON p.user_id = u.id WHERE p.user_id IN ($placeholders) AND p.status = 'active' AND p.visibility IN ('public', 'friends') AND p.created_at > DATE_SUB(NOW(), INTERVAL 7 DAY) ORDER BY p.created_at DESC LIMIT 200 "); $stmt->execute($userIds); return $stmt->fetchAll(); } // Get trending content private function getTrendingContent($limit) { $stmt = $this->pdo->prepare(" SELECT p.*, u.username, u.full_name, u.profile_pic, (p.likes_count * 1 + p.comments_count * 2 + p.shares_count * 3 + p.views_count * 0.1) as trend_score FROM posts p JOIN users u ON p.user_id = u.id WHERE p.status = 'active' AND p.visibility = 'public' AND p.created_at > DATE_SUB(NOW(), INTERVAL 3 DAY) ORDER BY trend_score DESC LIMIT ? "); $stmt->execute([$limit]); return $stmt->fetchAll(); } // Get trending hashtags public function getTrendingHashtags($limit = 10) { $stmt = $this->pdo->prepare(" SELECT tag, posts_count, last_used FROM hashtags WHERE last_used > DATE_SUB(NOW(), INTERVAL 7 DAY) ORDER BY posts_count DESC, last_used DESC LIMIT ? "); $stmt->execute([$limit]); return $stmt->fetchAll(); } // Get recommendation reasons private function getRecommendationReasons($post, $userProfile) { $reasons = []; if ($this->calculateInterestMatch($post, $userProfile) > 0.5) { $reasons[] = 'Based on your interests'; } if ($this->calculateHashtagMatch($post, $userProfile) > 0.3) { $reasons[] = 'You follow similar hashtags'; } if ($this->calculatePopularityScore($post) > 0.7) { $reasons[] = 'Trending in your network'; } return $reasons; } // Get friend recommendation reasons private function getFriendReasons($userId, $candidate) { $reasons = []; $mutualCount = getMutualFriendsCount($userId, $candidate['id']); if ($mutualCount > 0) { $reasons[] = "$mutualCount mutual friends"; } if ($this->calculateCommonInterestsScore($userId, $candidate['id']) > 0.5) { $reasons[] = 'Similar interests'; } return $reasons; } // Cache recommendations private function cacheRecommendations($userId, $type, $recommendations) { $table = $type == 'feed' ? 'content_recommendations' : 'friend_recommendations'; $idField = $type == 'feed' ? 'post_id' : 'suggested_user_id'; // Clear old cache $stmt = $this->pdo->prepare("DELETE FROM $table WHERE user_id = ?"); $stmt->execute([$userId]); // Insert new recommendations $stmt = $this->pdo->prepare(" INSERT INTO $table (user_id, $idField, score, reason, expires_at) VALUES (?, ?, ?, ?, DATE_ADD(NOW(), INTERVAL 1 DAY)) "); foreach ($recommendations as $rec) { $id = $type == 'feed' ? $rec['post']['id'] : $rec['user']['id']; $stmt->execute([ $userId, $id, $rec['score'], json_encode($rec['reasons'] ?? []) ]); } } // Get cached recommendations private function getCachedRecommendations($userId, $type, $limit) { $table = $type == 'feed' ? 'content_recommendations' : 'friend_recommendations'; $idField = $type == 'feed' ? 'post_id' : 'suggested_user_id'; $stmt = $this->pdo->prepare(" SELECT * FROM $table WHERE user_id = ? AND expires_at > NOW() ORDER BY score DESC LIMIT ? "); $stmt->execute([$userId, $limit]); return $stmt->fetchAll(); } // Helper: Get friend IDs private function getFriendIds($userId) { $stmt = $this->pdo->prepare(" SELECT CASE WHEN user_id = ? THEN friend_id ELSE user_id END as friend_id FROM friendships WHERE (user_id = ? OR friend_id = ?) AND status = 'accepted' "); $stmt->execute([$userId, $userId, $userId]); return $stmt->fetchAll(PDO::FETCH_COLUMN); } // Helper: Get following IDs private function getFollowingIds($userId) { $stmt = $this->pdo->prepare("SELECT following_id FROM follows WHERE follower_id = ?"); $stmt->execute([$userId]); return $stmt->fetchAll(PDO::FETCH_COLUMN); } // Helper: Get existing friend IDs private function getExistingFriendIds($userId) { $stmt = $this->pdo->prepare(" SELECT friend_id FROM friendships WHERE user_id = ? AND status = 'accepted' UNION SELECT user_id FROM friendships WHERE friend_id = ? AND status = 'accepted' "); $stmt->execute([$userId, $userId]); return $stmt->fetchAll(PDO::FETCH_COLUMN); } } ?>

classes/Message.php

<?php class Message { private $pdo; public function __construct($pdo) { $this->pdo = $pdo; } // Send message public function send($senderId, $receiverId, $content, $file = null) { $mediaUrl = null; if ($file) { $result = uploadFile($file, 'messages', ['jpg', 'jpeg', 'png', 'gif', 'mp4', 'mp3', 'pdf', 'doc', 'docx']); if ($result['success']) { $mediaUrl = $result['path']; } } // Insert message $stmt = $this->pdo->prepare(" INSERT INTO messages (sender_id, receiver_id, content, media_url) VALUES (?, ?, ?, ?) "); if ($stmt->execute([$senderId, $receiverId, $content, $mediaUrl])) { $messageId = $this->pdo->lastInsertId(); // Update or create conversation $this->updateConversation($senderId, $receiverId, $messageId); // Create notification createNotification($receiverId, $senderId, 'message', $messageId); return $this->getMessage($messageId); } return null; } // Get conversation between two users public function getConversation($userId1, $userId2, $limit = 50, $before = null) { $query = " SELECT m.*, u_sender.username as sender_username, u_sender.full_name as sender_name, u_sender.profile_pic as sender_pic FROM messages m JOIN users u_sender ON m.sender_id = u_sender.id WHERE (m.sender_id = ? AND m.receiver_id = ?) OR (m.sender_id = ? AND m.receiver_id = ?) "; $params = [$userId1, $userId2, $userId2, $userId1]; if ($before) { $query .= " AND m.id < ?"; $params[] = $before; } $query .= " ORDER BY m.created_at DESC LIMIT ?"; $params[] = $limit; $stmt = $this->pdo->prepare($query); $stmt->execute($params); $messages = $stmt->fetchAll(); // Mark messages as read $this->markAsRead($userId2, $userId1); return array_reverse($messages); } // Get all conversations for user public function getConversations($userId) { $stmt = $this->pdo->prepare(" SELECT c.*, u.id as other_user_id, u.username, u.full_name, u.profile_pic, m.content as last_message, m.created_at as last_message_time, m.sender_id as last_message_sender, (SELECT COUNT(*) FROM messages WHERE receiver_id = ? AND sender_id = u.id AND is_read = 0) as unread_count FROM conversations c JOIN users u ON (c.participant1_id = u.id OR c.participant2_id = u.id) LEFT JOIN messages m ON c.last_message_id = m.id WHERE (c.participant1_id = ? OR c.participant2_id = ?) AND u.id != ? ORDER BY c.last_message_time DESC "); $stmt->execute([$userId, $userId, $userId, $userId]); return $stmt->fetchAll(); } // Get single message private function getMessage($messageId) { $stmt = $this->pdo->prepare(" SELECT m.*, u_sender.username as sender_username, u_sender.full_name as sender_name, u_sender.profile_pic as sender_pic FROM messages m JOIN users u_sender ON m.sender_id = u_sender.id WHERE m.id = ? "); $stmt->execute([$messageId]); return $stmt->fetch(); } // Update conversation private function updateConversation($user1, $user2, $lastMessageId) { // Check if conversation exists $stmt = $this->pdo->prepare(" SELECT id FROM conversations WHERE (participant1_id = ? AND participant2_id = ?) OR (participant1_id = ? AND participant2_id = ?) "); $stmt->execute([$user1, $user2, $user2, $user1]); $conversation = $stmt->fetch(); if ($conversation) { // Update existing $stmt = $this->pdo->prepare(" UPDATE conversations SET last_message_id = ?, last_message_time = NOW() WHERE id = ? "); $stmt->execute([$lastMessageId, $conversation['id']]); } else { // Create new $stmt = $this->pdo->prepare(" INSERT INTO conversations (participant1_id, participant2_id, last_message_id, last_message_time) VALUES (?, ?, ?, NOW()) "); $stmt->execute([$user1, $user2, $lastMessageId]); } } // Mark messages as read public function markAsRead($userId, $otherUserId) { $stmt = $this->pdo->prepare(" UPDATE messages SET is_read = 1 WHERE receiver_id = ? AND sender_id = ? AND is_read = 0 "); $stmt->execute([$userId, $otherUserId]); } // Delete message public function delete($messageId, $userId) { $stmt = $this->pdo->prepare(" UPDATE messages SET is_deleted = 1 WHERE id = ? AND sender_id = ? "); return $stmt->execute([$messageId, $userId]); } } ?>

classes/Notification.php

<?php class Notification { private $pdo; public function __construct($pdo) { $this->pdo = $pdo; } // Get user notifications public function getUserNotifications($userId, $limit = 50, $offset = 0) { $stmt = $this->pdo->prepare(" SELECT n.*, u.username as actor_username, u.full_name as actor_name, u.profile_pic as actor_pic FROM notifications n LEFT JOIN users u ON n.actor_id = u.id WHERE n.user_id = ? ORDER BY n.created_at DESC LIMIT ? OFFSET ? "); $stmt->execute([$userId, $limit, $offset]); return $stmt->fetchAll(); } // Get unread count public function getUnreadCount($userId) { $stmt = $this->pdo->prepare(" SELECT COUNT(*) as count FROM notifications WHERE user_id = ? AND is_read = 0 "); $stmt->execute([$userId]); $result = $stmt->fetch(); return $result['count']; } // Mark as read public function markAsRead($notificationId, $userId) { $stmt = $this->pdo->prepare(" UPDATE notifications SET is_read = 1 WHERE id = ? AND user_id = ? "); return $stmt->execute([$notificationId, $userId]); } // Mark all as read public function markAllAsRead($userId) { $stmt = $this->pdo->prepare(" UPDATE notifications SET is_read = 1 WHERE user_id = ? AND is_read = 0 "); return $stmt->execute([$userId]); } // Create notification (static wrapper) public static function create($pdo, $userId, $actorId, $type, $referenceId = null, $content = null) { $stmt = $pdo->prepare(" INSERT INTO notifications (user_id, actor_id, type, reference_id, content) VALUES (?, ?, ?, ?, ?) "); return $stmt->execute([$userId, $actorId, $type, $referenceId, $content]); } } ?>

API ENDPOINTS

api/auth.php

<?php require_once '../config.php'; $action = $_GET['action'] ?? ''; switch ($action) { case 'login': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405); } $data = json_decode(file_get_contents('php://input'), true); $user = new User($pdo); $result = $user->login($data['username'], $data['password']); jsonResponse($result); break; case 'register': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405); } $data = json_decode(file_get_contents('php://input'), true); $user = new User($pdo); $result = $user->register($data); jsonResponse($result); break; case 'logout': session_destroy(); jsonResponse(['success' => true]); break; case 'check': jsonResponse([ 'logged_in' => isLoggedIn(), 'user' => getCurrentUser() ]); break; default: jsonResponse(['success' => false, 'message' => 'Invalid action'], 400); } ?>

api/posts.php

<?php require_once '../config.php'; if (!isLoggedIn()) { jsonResponse(['success' => false, 'message' => 'Not authenticated'], 401); } $action = $_GET['action'] ?? ''; $userId = getCurrentUserId(); switch ($action) { case 'create': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405); } $post = new Post($pdo); $result = $post->create($userId, $_POST, $_FILES); jsonResponse($result); break; case 'feed': $limit = $_GET['limit'] ?? 20; $offset = $_GET['offset'] ?? 0; $post = new Post($pdo); $posts = $post->getFeed($userId, $limit, $offset); jsonResponse(['success' => true, 'posts' => $posts]); break; case 'get': $postId = $_GET['id'] ?? 0; $post = new Post($pdo); $postData = $post->getById($postId, $userId); if ($postData) { jsonResponse(['success' => true, 'post' => $postData]); } else { jsonResponse(['success' => false, 'message' => 'Post not found'], 404); } break; case 'like': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405); } $data = json_decode(file_get_contents('php://input'), true); $post = new Post($pdo); $result = $post->like($data['post_id'], $userId, $data['reaction'] ?? 'like'); jsonResponse(['success' => true, 'result' => $result]); break; case 'comment': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405); } $data = json_decode(file_get_contents('php://input'), true); $post = new Post($pdo); $comment = $post->addComment($data['post_id'], $userId, $data['content'], $data['parent_id'] ?? null); if ($comment) { jsonResponse(['success' => true, 'comment' => $comment]); } else { jsonResponse(['success' => false, 'message' => 'Failed to add comment']); } break; case 'comments': $postId = $_GET['post_id'] ?? 0; $post = new Post($pdo); $comments = $post->getComments($postId); jsonResponse(['success' => true, 'comments' => $comments]); break; case 'share': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405); } $data = json_decode(file_get_contents('php://input'), true); $post = new Post($pdo); $result = $post->share($data['post_id'], $userId, $data['content'] ?? null, $data['visibility'] ?? 'public'); jsonResponse(['success' => $result]); break; default: jsonResponse(['success' => false, 'message' => 'Invalid action'], 400); } ?>

api/recommendations.php

<?php require_once '../config.php'; if (!isLoggedIn()) { jsonResponse(['success' => false, 'message' => 'Not authenticated'], 401); } $action = $_GET['action'] ?? 'feed'; $userId = getCurrentUserId(); $recommender = new SocialRecommender($pdo); switch ($action) { case 'feed': $limit = $_GET['limit'] ?? 20; $recommendations = $recommender->getRecommendations($userId, 'feed', $limit); jsonResponse(['success' => true, 'recommendations' => $recommendations]); break; case 'friends': $limit = $_GET['limit'] ?? 10; $recommendations = $recommender->getRecommendations($userId, 'friends', $limit); jsonResponse(['success' => true, 'recommendations' => $recommendations]); break; case 'trending': $limit = $_GET['limit'] ?? 20; $recommendations = $recommender->getRecommendations($userId, 'trending', $limit); jsonResponse(['success' => true, 'recommendations' => $recommendations]); break; case 'hashtags': $limit = $_GET['limit'] ?? 10; $hashtags = $recommender->getTrendingHashtags($limit); jsonResponse(['success' => true, 'hashtags' => $hashtags]); break; default: jsonResponse(['success' => false, 'message' => 'Invalid action'], 400); } ?>

api/users.php

<?php require_once '../config.php'; if (!isLoggedIn()) { jsonResponse(['success' => false, 'message' => 'Not authenticated'], 401); } $action = $_GET['action'] ?? ''; $userId = getCurrentUserId(); switch ($action) { case 'profile': $profileId = $_GET['id'] ?? $userId; $user = new User($pdo); $profile = $user->getById($profileId); if ($profile) { // Check friendship status $profile['friendship_status'] = getFriendshipStatus($userId, $profileId); $profile['is_following'] = isFollowing($userId, $profileId); $profile['mutual_friends'] = getMutualFriendsCount($userId, $profileId); jsonResponse(['success' => true, 'profile' => $profile]); } else { jsonResponse(['success' => false, 'message' => 'User not found'], 404); } break; case 'search': $query = $_GET['q'] ?? ''; $user = new User($pdo); $results = $user->search($query); jsonResponse(['success' => true, 'users' => $results]); break; case 'friends': $targetId = $_GET['id'] ?? $userId; $user = new User($pdo); $friends = $user->getFriends($targetId); jsonResponse(['success' => true, 'friends' => $friends]); break; case 'followers': $targetId = $_GET['id'] ?? $userId; $user = new User($pdo); $followers = $user->getFollowers($targetId); jsonResponse(['success' => true, 'followers' => $followers]); break; case 'following': $targetId = $_GET['id'] ?? $userId; $user = new User($pdo); $following = $user->getFollowing($targetId); jsonResponse(['success' => true, 'following' => $following]); break; case 'update': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405); } $data = json_decode(file_get_contents('php://input'), true); $user = new User($pdo); $result = $user->updateProfile($userId, $data); jsonResponse(['success' => $result]); break; case 'upload_profile_pic': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405); } $user = new User($pdo); $result = $user->updateProfilePic($userId, $_FILES['image']); jsonResponse(['success' => $result]); break; case 'upload_cover': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405); } $user = new User($pdo); $result = $user->updateCoverPhoto($userId, $_FILES['image']); jsonResponse(['success' => $result]); break; case 'friend_request': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405); } $data = json_decode(file_get_contents('php://input'), true); $friendId = $data['user_id']; // Check if request exists $stmt = $pdo->prepare(" SELECT id FROM friendships WHERE (user_id = ? AND friend_id = ?) OR (user_id = ? AND friend_id = ?) "); $stmt->execute([$userId, $friendId, $friendId, $userId]); if ($stmt->fetch()) { jsonResponse(['success' => false, 'message' => 'Friend request already exists']); } // Create request $stmt = $pdo->prepare(" INSERT INTO friendships (user_id, friend_id, status, action_user_id) VALUES (?, ?, 'pending', ?) "); if ($stmt->execute([$userId, $friendId, $userId])) { createNotification($friendId, $userId, 'friend_request'); jsonResponse(['success' => true]); } else { jsonResponse(['success' => false]); } break; case 'accept_friend': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405); } $data = json_decode(file_get_contents('php://input'), true); $friendId = $data['user_id']; $stmt = $pdo->prepare(" UPDATE friendships SET status = 'accepted' WHERE user_id = ? AND friend_id = ? AND status = 'pending' "); if ($stmt->execute([$friendId, $userId])) { createNotification($friendId, $userId, 'friend_accept'); jsonResponse(['success' => true]); } else { jsonResponse(['success' => false]); } break; case 'follow': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405); } $data = json_decode(file_get_contents('php://input'), true); $followingId = $data['user_id']; $stmt = $pdo->prepare("INSERT IGNORE INTO follows (follower_id, following_id) VALUES (?, ?)"); $result = $stmt->execute([$userId, $followingId]); if ($result) { createNotification($followingId, $userId, 'follow'); } jsonResponse(['success' => $result]); break; case 'unfollow': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405); } $data = json_decode(file_get_contents('php://input'), true); $followingId = $data['user_id']; $stmt = $pdo->prepare("DELETE FROM follows WHERE follower_id = ? AND following_id = ?"); $result = $stmt->execute([$userId, $followingId]); jsonResponse(['success' => $result]); break; default: jsonResponse(['success' => false, 'message' => 'Invalid action'], 400); } ?>

api/messages.php

<?php require_once '../config.php'; if (!isLoggedIn()) { jsonResponse(['success' => false, 'message' => 'Not authenticated'], 401); } $action = $_GET['action'] ?? ''; $userId = getCurrentUserId(); $message = new Message($pdo); switch ($action) { case 'send': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405); } $receiverId = $_POST['receiver_id']; $content = $_POST['content'] ?? ''; $file = $_FILES['file'] ?? null; $result = $message->send($userId, $receiverId, $content, $file); if ($result) { jsonResponse(['success' => true, 'message' => $result]); } else { jsonResponse(['success' => false, 'message' => 'Failed to send message']); } break; case 'conversation': $otherUserId = $_GET['user_id'] ?? 0; $limit = $_GET['limit'] ?? 50; $before = $_GET['before'] ?? null; $messages = $message->getConversation($userId, $otherUserId, $limit, $before); jsonResponse(['success' => true, 'messages' => $messages]); break; case 'list': $conversations = $message->getConversations($userId); jsonResponse(['success' => true, 'conversations' => $conversations]); break; case 'delete': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405); } $data = json_decode(file_get_contents('php://input'), true); $result = $message->delete($data['message_id'], $userId); jsonResponse(['success' => $result]); break; default: jsonResponse(['success' => false, 'message' => 'Invalid action'], 400); } ?>

api/notifications.php

<?php require_once '../config.php'; if (!isLoggedIn()) { jsonResponse(['success' => false, 'message' => 'Not authenticated'], 401); } $action = $_GET['action'] ?? ''; $userId = getCurrentUserId(); $notification = new Notification($pdo); switch ($action) { case 'list': $limit = $_GET['limit'] ?? 50; $offset = $_GET['offset'] ?? 0; $notifications = $notification->getUserNotifications($userId, $limit, $offset); $unreadCount = $notification->getUnreadCount($userId); jsonResponse([ 'success' => true, 'notifications' => $notifications, 'unread_count' => $unreadCount ]); break; case 'mark_read': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405); } $data = json_decode(file_get_contents('php://input'), true); if (isset($data['all']) && $data['all']) { $result = $notification->markAllAsRead($userId); } else { $result = $notification->markAsRead($data['notification_id'], $userId); } jsonResponse(['success' => $result]); break; case 'count': $count = $notification->getUnreadCount($userId); jsonResponse(['success' => true, 'count' => $count]); break; default: jsonResponse(['success' => false, 'message' => 'Invalid action'], 400); } ?>

api/search.php

<?php require_once '../config.php'; if (!isLoggedIn()) { jsonResponse(['success' => false, 'message' => 'Not authenticated'], 401); } $query = $_GET['q'] ?? ''; $type = $_GET['type'] ?? 'all'; $limit = $_GET['limit'] ?? 20; $offset = $_GET['offset'] ?? 0; if (empty($query)) { jsonResponse(['success' => false, 'message' => 'Search query required'], 400); } $results = []; // Search users if ($type == 'all' || $type == 'users') { $user = new User($pdo); $results['users'] = $user->search($query, $limit, $offset); } // Search posts if ($type == 'all' || $type == 'posts') { $stmt = $pdo->prepare(" SELECT p.*, u.username, u.full_name, u.profile_pic FROM posts p JOIN users u ON p.user_id = u.id WHERE MATCH(p.content) AGAINST(?) AND p.status = 'active' AND p.visibility = 'public' ORDER BY p.created_at DESC LIMIT ? OFFSET ? "); $stmt->execute([$query, $limit, $offset]); $results['posts'] = $stmt->fetchAll(); } // Search hashtags if ($type == 'all' || $type == 'hashtags') { $stmt = $pdo->prepare(" SELECT * FROM hashtags WHERE tag LIKE ? ORDER BY posts_count DESC LIMIT ? OFFSET ? "); $stmt->execute(["%$query%", $limit, $offset]); $results['hashtags'] = $stmt->fetchAll(); } jsonResponse(['success' => true, 'results' => $results]); ?>

FRONTEND IMPLEMENTATION

index.html (Main Application)

```html





SocialConnect - Connect with the World

<!-- Tailwind CSS --> <script src="https://cdn.tailwindcss.com"></script> <!-- Font Awesome --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> <!-- Custom CSS --> <style> /* Custom animations */ @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } .animate-fade-in { animation: fadeIn 0.3s ease-out; } /* Scrollbar styling */ ::-webkit-scrollbar { width: 8px; height: 8px; } ::-webkit-scrollbar-track { background: #f1f1f1; } ::-webkit-scrollbar-thumb { background: #888; border-radius: 4px; } ::-webkit-scrollbar-thumb:hover { background: #555; } /* Post hover effects */ .post-card { transition: transform 0.2s, box-shadow 0.2s; } .post-card:hover { transform: translateY(-2px); box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1); } /* Loading spinner */ .loading-spinner { border: 3px solid #f3f3f3; border-top: 3px solid #3b82f6; border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } /* Story ring animation */ .story-ring { background: linear-gradient(45deg, #f09433, #d

Social Media Platform - Frontend Implementation (Continued)

 background: linear-gradient(45deg, #f09433, #d66837, #dc2743, #cc2366, #bc1888); padding: 2px; border-radius: 50%; animation: pulse 2s infinite; } @keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.05); } 100% { transform: scale(1); } } /* Message bubble */ .message-bubble-sent { background: #3b82f6; color: white; border-radius: 18px 18px 4px 18px; } .message-bubble-received { background: #f3f4f6; color: #1f2937; border-radius: 18px 18px 18px 4px; } /* Notification badge */ .notification-badge { position: absolute; top: -5px; right: -5px; background: #ef4444; color: white; border-radius: 50%; min-width: 18px; height: 18px; font-size: 11px; display: flex; align-items: center; justify-content: center; padding: 0 4px; } /* Mobile menu */ .mobile-menu-enter { transform: translateX(-100%); } .mobile-menu-enter-active { transform: translateX(0); transition: transform 0.3s ease-out; } </style> </head> <body class="bg-gray-100"> <!-- Loading Overlay --> <div id="loadingOverlay" class="fixed inset-0 bg-white z-50 flex items-center justify-center hidden"> <div class="loading-spinner"></div> </div> <!-- Navbar --> <nav class="bg-white shadow-lg sticky top-0 z-40"> <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div class="flex justify-between h-16"> <!-- Logo and brand --> <div class="flex items-center"> <div class="flex-shrink-0 flex items-center cursor-pointer" onclick="window.location='index.html'"> <i class="fab fa-connectdevelop text-3xl text-blue-600"></i> <span class="ml-2 text-xl font-bold text-gray-800 hidden sm:block">SocialConnect</span> </div> <!-- Search bar --> <div class="hidden md:block ml-6"> <div class="relative"> <input type="text" id="searchInput" placeholder="Search people, posts, hashtags..." class="w-96 px-4 py-2 pl-10 pr-4 rounded-full border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" onkeypress="if(event.key === 'Enter') search()"> <i class="fas fa-search absolute left-3 top-3 text-gray-400"></i> </div> </div> </div> <!-- Navigation links (desktop) --> <div class="hidden md:flex items-center space-x-4"> <a href="#" onclick="loadHome()" class="px-3 py-2 rounded-md text-sm font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50"> <i class="fas fa-home"></i> <span class="ml-1">Home</span> </a> <a href="#" onclick="loadFriends()" class="px-3 py-2 rounded-md text-sm font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50"> <i class="fas fa-users"></i> <span class="ml-1">Friends</span> </a> <a href="#" onclick="loadMessages()" class="px-3 py-2 rounded-md text-sm font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50 relative"> <i class="fas fa-comment"></i> <span class="ml-1">Messages</span> <span id="messageBadge" class="notification-badge hidden">0</span> </a> <a href="#" onclick="loadNotifications()" class="px-3 py-2 rounded-md text-sm font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50 relative"> <i class="fas fa-bell"></i> <span class="ml-1">Notifications</span> <span id="notificationBadge" class="notification-badge hidden">0</span> </a> <!-- Profile dropdown --> <div class="relative" id="profileDropdown"> <button onclick="toggleProfileDropdown()" class="flex items-center space-x-2 focus:outline-none"> <img id="navProfilePic" src="assets/images/default-avatar.png" alt="Profile" class="w-8 h-8 rounded-full object-cover"> <span id="navUsername" class="text-sm font-medium text-gray-700">Loading...</span> <i class="fas fa-chevron-down text-gray-500 text-xs"></i> </button> <div id="profileMenu" class="hidden absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 z-50"> <a href="#" onclick="loadProfile()" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"> <i class="fas fa-user mr-2"></i>Profile </a> <a href="#" onclick="loadSettings()" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"> <i class="fas fa-cog mr-2"></i>Settings </a> <hr class="my-1"> <a href="#" onclick="logout()" class="block px-4 py-2 text-sm text-red-600 hover:bg-gray-100"> <i class="fas fa-sign-out-alt mr-2"></i>Logout </a> </div> </div> </div> <!-- Mobile menu button --> <div class="flex items-center md:hidden"> <button onclick="toggleMobileMenu()" class="inline-flex items-center justify-center p-2 rounded-md text-gray-700 hover:text-gray-900 hover:bg-gray-100 focus:outline-none"> <i class="fas fa-bars text-2xl"></i> </button> </div> </div> </div> <!-- Mobile menu --> <div id="mobileMenu" class="hidden md:hidden bg-white border-t border-gray-200 py-2"> <div class="px-4 py-2"> <div class="relative"> <input type="text" id="mobileSearchInput" placeholder="Search..." class="w-full px-4 py-2 pl-10 pr-4 rounded-full border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500" onkeypress="if(event.key === 'Enter') search()"> <i class="fas fa-search absolute left-3 top-3 text-gray-400"></i> </div> </div> <a href="#" onclick="loadHome()" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"> <i class="fas fa-home w-6"></i> Home </a> <a href="#" onclick="loadFriends()" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"> <i class="fas fa-users w-6"></i> Friends </a> <a href="#" onclick="loadMessages()" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 relative"> <i class="fas fa-comment w-6"></i> Messages <span id="mobileMessageBadge" class="notification-badge hidden">0</span> </a> <a href="#" onclick="loadNotifications()" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 relative"> <i class="fas fa-bell w-6"></i> Notifications <span id="mobileNotificationBadge" class="notification-badge hidden">0</span> </a> <hr class="my-2"> <a href="#" onclick="loadProfile()" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"> <i class="fas fa-user w-6"></i> Profile </a> <a href="#" onclick="loadSettings()" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"> <i class="fas fa-cog w-6"></i> Settings </a> <a href="#" onclick="logout()" class="block px-4 py-2 text-sm text-red-600 hover:bg-gray-100"> <i class="fas fa-sign-out-alt w-6"></i> Logout </a> </div> </nav> <!-- Main Content --> <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6"> <div class="flex flex-col lg:flex-row gap-6"> <!-- Left Sidebar (desktop) --> <div class="hidden lg:block lg:w-64 space-y-4"> <!-- User info card --> <div class="bg-white rounded-lg shadow p-4"> <div class="flex items-center space-x-3"> <img id="sidebarProfilePic" src="assets/images/default-avatar.png" alt="Profile" class="w-12 h-12 rounded-full object-cover"> <div> <h3 id="sidebarName" class="font-semibold">Loading...</h3> <p id="sidebarUsername" class="text-sm text-gray-500">@loading</p> </div> </div> <div class="mt-4 grid grid-cols-3 gap-2 text-center text-sm"> <div> <div id="sidebarPostsCount" class="font-semibold">0</div> <div class="text-gray-500">Posts</div> </div> <div> <div id="sidebarFriendsCount" class="font-semibold">0</div> <div class="text-gray-500">Friends</div> </div> <div> <div id="sidebarFollowersCount" class="font-semibold">0</div> <div class="text-gray-500">Followers</div> </div> </div> </div> <!-- Trending topics --> <div class="bg-white rounded-lg shadow p-4"> <h3 class="font-semibold mb-3 flex items-center"> <i class="fas fa-fire text-orange-500 mr-2"></i> Trending </h3> <div id="trendingList" class="space-y-2"> <!-- Loaded via JavaScript --> </div> </div> <!-- Friend suggestions --> <div class="bg-white rounded-lg shadow p-4"> <h3 class="font-semibold mb-3 flex items-center"> <i class="fas fa-user-friends text-blue-500 mr-2"></i> Suggested Friends </h3> <div id="friendSuggestions" class="space-y-3"> <!-- Loaded via JavaScript --> </div> <a href="#" onclick="loadMoreSuggestions()" class="block text-center text-sm text-blue-600 hover:text-blue-800 mt-3"> See More </a> </div> </div> <!-- Main Content Area --> <div class="flex-1"> <div id="contentArea"> <!-- Content loaded dynamically --> </div> </div> <!-- Right Sidebar (desktop) --> <div class="hidden lg:block lg:w-80 space-y-4"> <!-- Create post --> <div class="bg-white rounded-lg shadow p-4"> <div class="flex items-center space-x-3"> <img id="createPostAvatar" src="assets/images/default-avatar.png" alt="Profile" class="w-10 h-10 rounded-full object-cover"> <button onclick="openCreatePostModal()" class="flex-1 text-left px-4 py-2 bg-gray-100 hover:bg-gray-200 rounded-full text-gray-500 text-sm"> What's on your mind? </button> </div> <div class="mt-3 flex justify-around"> <button onclick="openCreatePostModal('photo')" class="flex items-center space-x-1 text-gray-600 hover:text-blue-600"> <i class="fas fa-image text-green-500"></i> <span class="text-sm">Photo</span> </button> <button onclick="openCreatePostModal('video')" class="flex items-center space-x-1 text-gray-600 hover:text-blue-600"> <i class="fas fa-video text-blue-500"></i> <span class="text-sm">Video</span> </button> <button onclick="openCreatePostModal()" class="flex items-center space-x-1 text-gray-600 hover:text-blue-600"> <i class="fas fa-smile text-yellow-500"></i> <span class="text-sm">Feeling</span> </button> </div> </div> <!-- Stories --> <div class="bg-white rounded-lg shadow p-4"> <h3 class="font-semibold mb-3 flex items-center justify-between"> <span>Stories</span> <a href="#" class="text-sm text-blue-600 hover:text-blue-800">See All</a> </h3> <div id="storiesList" class="space-y-3"> <!-- Loaded via JavaScript --> </div> </div> <!-- Upcoming birthdays --> <div class="bg-white rounded-lg shadow p-4"> <h3 class="font-semibold mb-3 flex items-center"> <i class="fas fa-gift text-pink-500 mr-2"></i> Birthdays </h3> <div id="birthdaysList"> <!-- Loaded via JavaScript --> </div> </div> </div> </div> </div> <!-- Create Post Modal --> <div id="createPostModal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden items-center justify-center"> <div class="bg-white rounded-lg w-full max-w-lg mx-4 animate-fade-in"> <div class="flex justify-between items-center p-4 border-b"> <h3 class="text-lg font-semibold">Create Post</h3> <button onclick="closeCreatePostModal()" class="text-gray-500 hover:text-gray-700"> <i class="fas fa-times text-xl"></i> </button> </div> <div class="p-4"> <div class="flex items-center space-x-3 mb-4"> <img id="modalProfilePic" src="assets/images/default-avatar.png" alt="Profile" class="w-10 h-10 rounded-full object-cover"> <div> <h4 id="modalName" class="font-semibold">Loading...</h4> <select id="postVisibility" class="text-sm border rounded-md px-2 py-1"> <option value="public">Public</option> <option value="friends">Friends</option> <option value="private">Only Me</option> </select> </div> </div> <textarea id="postContent" rows="4" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="What's on your mind?"></textarea> <div id="mediaPreview" class="mt-4 grid grid-cols-2 gap-2 hidden"> <!-- Media preview will be shown here --> </div> <div class="mt-4 border rounded-lg p-3"> <div class="flex justify-between items-center"> <span class="font-medium">Add to your post</span> <div class="flex space-x-2"> <label class="cursor-pointer"> <i class="fas fa-image text-green-500 text-xl hover:opacity-75"></i> <input type="file" id="postImage" accept="image/*" multiple class="hidden" onchange="previewMedia()"> </label> <label class="cursor-pointer"> <i class="fas fa-video text-blue-500 text-xl hover:opacity-75"></i> <input type="file" id="postVideo" accept="video/*" class="hidden" onchange="previewMedia()"> </label> <label class="cursor-pointer"> <i class="fas fa-smile text-yellow-500 text-xl hover:opacity-75"></i> <input type="file" id="postAudio" accept="audio/*" class="hidden" onchange="previewMedia()"> </label> </div> </div> </div> </div> <div class="p-4 border-t"> <button onclick="createPost()" class="w-full bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 transition"> Post </button> </div> </div> </div> <!-- Login Modal --> <div id="loginModal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden items-center justify-center"> <div class="bg-white rounded-lg w-full max-w-md mx-4 p-6 animate-fade-in"> <div class="text-center mb-6"> <i class="fab fa-connectdevelop text-5xl text-blue-600"></i> <h2 class="text-2xl font-bold mt-2">Welcome Back</h2> <p class="text-gray-600">Sign in to continue</p> </div> <form onsubmit="login(event)"> <div class="mb-4"> <label class="block text-sm font-medium text-gray-700 mb-2">Username or Email</label> <input type="text" id="loginUsername" required class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"> </div> <div class="mb-4"> <label class="block text-sm font-medium text-gray-700 mb-2">Password</label> <input type="password" id="loginPassword" required class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"> </div> <button type="submit" class="w-full bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 transition mb-4"> Sign In </button> <p class="text-center text-sm text-gray-600"> Don't have an account? <a href="#" onclick="showRegisterModal()" class="text-blue-600 hover:text-blue-800">Sign Up</a> </p> </form> </div> </div> <!-- Register Modal --> <div id="registerModal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden items-center justify-center"> <div class="bg-white rounded-lg w-full max-w-md mx-4 p-6 animate-fade-in"> <div class="text-center mb-6"> <i class="fab fa-connectdevelop text-5xl text-blue-600"></i> <h2 class="text-2xl font-bold mt-2">Create Account</h2> <p class="text-gray-600">Join our community</p> </div> <form onsubmit="register(event)"> <div class="mb-4"> <label class="block text-sm font-medium text-gray-700 mb-2">Username</label> <input type="text" id="regUsername" required class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"> </div> <div class="mb-4"> <label class="block text-sm font-medium text-gray-700 mb-2">Email</label> <input type="email" id="regEmail" required class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"> </div> <div class="mb-4"> <label class="block text-sm font-medium text-gray-700 mb-2">Full Name</label> <input type="text" id="regFullName" required class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"> </div> <div class="mb-4"> <label class="block text-sm font-medium text-gray-700 mb-2">Password</label> <input type="password" id="regPassword" required class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"> </div> <div class="mb-4"> <label class="block text-sm font-medium text-gray-700 mb-2">Birth Date</label> <input type="date" id="regBirthDate" required class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"> </div> <div class="mb-4"> <label class="block text-sm font-medium text-gray-700 mb-2">Gender</label> <select id="regGender" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"> <option value="male">Male</option> <option value="female">Female</option> <option value="other">Other</option> <option value="prefer_not_to_say">Prefer not to say</option> </select> </div> <button type="submit" class="w-full bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 transition mb-4"> Sign Up </button> <p class="text-center text-sm text-gray-600"> Already have an account? <a href="#" onclick="showLoginModal()" class="text-blue-600 hover:text-blue-800">Sign In</a> </p> </form> </div> </div> <!-- JavaScript --> <script> // State management let currentUser = null; let currentPage = 'home'; let ws = null; let notificationInterval = null; // Initialize on load document.addEventListener('DOMContentLoaded', function() { checkAuth(); initializeWebSocket(); startPolling(); }); // Check authentication function checkAuth() { showLoading(); fetch('api/auth.php?action=check') .then(response => response.json()) .then(data => { if (data.logged_in) { currentUser = data.user; updateUI(); loadHome(); } else { showLoginModal(); } }) .finally(() => { hideLoading(); }); } // Login function login(event) { event.preventDefault(); showLoading(); const username = document.getElementById('loginUsername').value; const password = document.getElementById('loginPassword').value; fetch('api/auth.php?action=login', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ username, password }) }) .then(response => response.json()) .then(data => { if (data.success) { closeModals(); currentUser = data.user; updateUI(); loadHome(); initializeWebSocket(); } else { alert('Login failed: ' + data.message); } }) .finally(() => { hideLoading(); }); } // Register function register(event) { event.preventDefault(); showLoading(); const userData = { username: document.getElementById('regUsername').value, email: document.getElementById('regEmail').value, full_name: document.getElementById('regFullName').value, password: document.getElementById('regPassword').value, birth_date: document.getElementById('regBirthDate').value, gender: document.getElementById('regGender').value }; fetch('api/auth.php?action=register', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(userData) }) .then(response => response.json()) .then(data => { if (data.success) { closeModals(); showLoginModal(); alert('Registration successful! Please login.'); } else { alert('Registration failed: ' + data.message); } }) .finally(() => { hideLoading(); }); } // Logout function logout() { fetch('api/auth.php?action=logout') .then(() => { currentUser = null; if (ws) { ws.close(); } if (notificationInterval) { clearInterval(notificationInterval); } showLoginModal(); }); } // Update UI with user data function updateUI() { if (!currentUser) return; // Update profile pictures document.getElementById('navProfilePic').src = currentUser.profile_pic || 'assets/images/default-avatar.png'; document.getElementById('sidebarProfilePic').src = currentUser.profile_pic || 'assets/images/default-avatar.png'; document.getElementById('createPostAvatar').src = currentUser.profile_pic || 'assets/images/default-avatar.png'; document.getElementById('modalProfilePic').src = currentUser.profile_pic || 'assets/images/default-avatar.png'; // Update names document.getElementById('navUsername').textContent = currentUser.full_name || currentUser.username; document.getElementById('sidebarName').textContent = currentUser.full_name || currentUser.username; document.getElementById('sidebarUsername').textContent = '@' + currentUser.username; document.getElementById('modalName').textContent = currentUser.full_name || currentUser.username; // Update counts document.getElementById('sidebarPostsCount').textContent = currentUser.posts_count || 0; document.getElementById('sidebarFriendsCount').textContent = currentUser.friends_count || 0; document.getElementById('sidebarFollowersCount').textContent = currentUser.followers_count || 0; } // Load home feed function loadHome() { currentPage = 'home'; showLoading(); Promise.all([ fetch('api/posts.php?action=feed').then(r => r.json()), fetch('api/recommendations.php?action=feed').then(r => r.json()), fetch('api/recommendations.php?action=trending').then(r => r.json()), loadStories() ]) .then(([feedData, recData, trendingData]) => { const content = document.getElementById('contentArea'); content.innerHTML = ` <div class="space-y-6"> <!-- Create post (mobile) --> <div class="lg:hidden bg-white rounded-lg shadow p-4"> <div class="flex items-center space-x-3"> <img src="${currentUser.profile_pic || 'assets/images/default-avatar.png'}" class="w-10 h-10 rounded-full object-cover"> <button onclick="openCreatePostModal()" class="flex-1 text-left px-4 py-2 bg-gray-100 rounded-full text-gray-500 text-sm"> What's on your mind? </button> </div> </div> <!-- Stories (mobile) --> <div class="lg:hidden bg-white rounded-lg shadow p-4"> <div class="flex justify-between items-center mb-3"> <h3 class="font-semibold">Stories</h3> <a href="#" class="text-sm text-blue-600">See All</a> </div> <div id="mobileStories" class="flex space-x-4 overflow-x-auto pb-2"> ${renderStoriesHTML()} </div> </div> <!-- Feed Tabs --> <div class="bg-white rounded-lg shadow"> <div class="flex border-b"> <button onclick="switchFeed('latest')" class="flex-1 px-4 py-3 text-sm font-medium ${getActiveFeedTab('latest')}"> Latest </button> <button onclick="switchFeed('recommended')" class="flex-1 px-4 py-3 text-sm font-medium ${getActiveFeedTab('recommended')}"> Recommended </button> <button onclick="switchFeed('trending')" class="flex-1 px-4 py-3 text-sm font-medium ${getActiveFeedTab('trending')}"> Trending </button> </div> <div id="feedContent" class="p-4"> ${renderFeed(feedData.posts)} </div> <div class="p-4 border-t text-center"> <button onclick="loadMorePosts()" class="text-blue-600 hover:text-blue-800 text-sm"> Load More </button> </div> </div> </div> `; // Load recommendations into sidebar loadSidebarContent(recData, trendingData); }) .finally(() => { hideLoading(); }); } // Render feed posts function renderFeed(posts) { if (!posts || posts.length === 0) { return '<p class="text-center text-gray-500 py-8">No posts to show</p>'; } return posts.map(post => ` <div class="post-card bg-white rounded-lg shadow mb-4 p-4" data-post-id="${post.id}"> <div class="flex items-center justify-between mb-3"> <div class="flex items-center space-x-3"> <img src="${post.profile_pic || 'assets/images/default-avatar.png'}" alt="${post.full_name}" class="w-10 h-10 rounded-full object-cover cursor-pointer" onclick="loadUserProfile(${post.user_id})"> <div> <h4 class="font-semibold cursor-pointer hover:underline" onclick="loadUserProfile(${post.user_id})"> ${post.full_name} </h4> <p class="text-xs text-gray-500">${timeAgo(post.created_at)}</p> </div> </div> <button onclick="showPostMenu(${post.id})" class="text-gray-500 hover:text-gray-700"> <i class="fas fa-ellipsis-h"></i> </button> </div> <div class="mb-3"> <p class="text-gray-800">${post.content}</p> </div> ${renderPostMedia(post)} <div class="flex items-center justify-between mt-3 pt-3 border-t"> <button onclick="likePost(${post.id})" class="flex items-center space-x-1 text-gray-600 hover:text-blue-600"> <i class="fas fa-thumbs-up ${post.user_liked ? 'text-blue-600' : ''}"></i> <span>${post.likes_count || 0}</span> </button> <button onclick="showComments(${post.id})" class="flex items-center space-x-1 text-gray-600 hover:text-blue-600"> <i class="fas fa-comment"></i> <span>${post.comments_count || 0}</span> </button> <button onclick="sharePost(${post.id})" class="flex items-center space-x-1 text-gray-600 hover:text-blue-600"> <i class="fas fa-share"></i> <span>${post.shares_count || 0}</span> </button> </div> <!-- Comments section (hidden by default) --> <div id="comments-${post.id}" class="hidden mt-4 pt-4 border-t"> <div class="space-y-3" id="comments-list-${post.id}"> <!-- Comments loaded dynamically --> </div> <div class="flex items-center space-x-2 mt-3"> <img src="${currentUser.profile_pic || 'assets/images/default-avatar.png'}" class="w-8 h-8 rounded-full object-cover"> <input type="text" id="comment-${post.id}" placeholder="Write a comment..." class="flex-1 px-3 py-2 bg-gray-100 rounded-full text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" onkeypress="if(event.key === 'Enter') addComment(${post.id})"> </div> </div> </div> `).join(''); } // Render post media function renderPostMedia(post) { if (!post.media_url || post.media_url.length === 0) return ''; const media = JSON.parse(post.media_url); if (media.length === 0) return ''; if (post.media_type === 'image') { if (media.length === 1) { return ` <div class="mb-3 rounded-lg overflow-hidden"> <img src="${media[0]}" alt="Post image" class="w-full object-cover"> </div> `; } else { return ` <div class="mb-3 grid grid-cols-${Math.min(media.length, 2)} gap-1"> ${media.map(url => ` <img src="${url}" alt="Post image" class="w-full h-48 object-cover rounded"> `).join('')} </div> `; } } if (post.media_type === 'video') { return ` <div class="mb-3 rounded-lg overflow-hidden"> <video src="${media[0]}" controls class="w-full"></video> </div> `; } return ''; } // Load sidebar content function loadSidebarContent(recData, trendingData) { // Update trending const trendingList = document.getElementById('trendingList'); if (trendingList) { trendingList.innerHTML = trendingData.trending?.map(item => ` <a href="#" onclick="searchHashtag('${item.tag}')" class="block hover:bg-gray-50 p-2 rounded"> <p class="font-medium text-blue-600">#${item.tag}</p> <p class="text-xs text-gray-500">${item.posts_count} posts</p> </a> `).join('') || '<p class="text-gray-500 text-sm">No trending topics</p>'; } // Update friend suggestions const suggestions = document.getElementById('friendSuggestions'); if (suggestions && recData.recommendations) { suggestions.innerHTML = recData.recommendations.map(suggestion => ` <div class="flex items-center justify-between"> <div class="flex items-center space-x-2"> <img src="${suggestion.user.profile_pic || 'assets/images/default-avatar.png'}" alt="${suggestion.user.full_name}" class="w-8 h-8 rounded-full object-cover cursor-pointer" onclick="loadUserProfile(${suggestion.user.id})"> <div> <p class="text-sm font-medium cursor-pointer hover:underline" onclick="loadUserProfile(${suggestion.user.id})"> ${suggestion.user.full_name} </p> <p class="text-xs text-gray-500">${suggestion.reasons?.join(' • ') || ''}</p> </div> </div> <button onclick="sendFriendRequest(${suggestion.user.id})" class="text-blue-600 hover:text-blue-800"> <i class="fas fa-user-plus"></i> </button> </div> `).join(''); } } // Load stories function loadStories() { // Mock stories for now return Promise.resolve(); } // Render stories HTML function renderStoriesHTML() { const stories = [ { id: 1, name: 'Your Story', image: currentUser.profile_pic, isUser: true }, { id: 2, name: 'John Doe', image: null }, { id: 3, name: 'Jane Smith', image: null }, { id: 4, name: 'Mike Johnson', image: null } ]; return stories.map(story => ` <div class="flex-shrink-0 text-center"> <div class="story-ring"> <img src="${story.image || 'assets/images/default-avatar.png'}" alt="${story.name}" class="w-14 h-14 rounded-full object-cover border-2 border-white"> </div> <p class="text-xs mt-1 max-w-[80px] truncate">${story.name}</p> </div> `).join(''); } // Load user profile function loadUserProfile(userId) { showLoading(); fetch(`api/users.php?action=profile&id=${userId}`) .then(response => response.json()) .then(data => { if (data.success) { displayUserProfile(data.profile); } }) .finally(() => { hideLoading(); }); } // Display user profile function displayUserProfile(profile) { const content = document.getElementById('contentArea'); content.innerHTML = ` <div class="bg-white rounded-lg shadow overflow-hidden"> <!-- Cover Photo --> <div class="h-48 bg-gradient-to-r from-blue-500 to-purple-600 relative"> <img src="${profile.cover_photo || 'assets/images/default-cover.jpg'}" alt="Cover" class="w-full h-full object-cover"> <!-- Profile Picture --> <div class="absolute -bottom-16 left-8"> <div class="relative"> <img src="${profile.profile_pic || 'assets/images/default-avatar.png'}" alt="${profile.full_name}" class="w-32 h-32 rounded-full border-4 border-white object-cover"> ${profile.user_id === currentUser.id ? ` <label class="absolute bottom-0 right-0 bg-blue-600 text-white p-2 rounded-full cursor-pointer hover:bg-blue-700"> <i class="fas fa-camera text-sm"></i> <input type="file" id="profilePicUpload" accept="image/*" class="hidden" onchange="uploadProfilePic()"> </label> ` : ''} </div> </div> <!-- Cover Photo Edit (if own profile) --> ${profile.user_id === currentUser.id ? ` <label class="absolute top-4 right-4 bg-black bg-opacity-50 text-white px-4 py-2 rounded-lg cursor-pointer hover:bg-opacity-70"> <i class="fas fa-camera mr-2"></i>Edit Cover <input type="file" id="coverPhotoUpload" accept="image/*" class="hidden" onchange="uploadCoverPhoto()"> </label> ` : ''} </div> <!-- Profile Info --> <div class="pt-20 px-8 pb-6"> <div class="flex justify-between items-start"> <div> <h2 class="text-2xl font-bold">${profile.full_name}</h2> <p class="text-gray-600">@${profile.username}</p> <p class="mt-2 text-gray-700">${profile.bio || 'No bio yet'}</p> <div class="flex space-x-6 mt-4"> <div> <span class="font-bold">${profile.posts_count || 0}</span> <span class="text-gray-600">Posts</span> </div> <div> <span class="font-bold">${profile.friends_count || 0}</span> <span class="text-gray-600">Friends</span> </div> <div> <span class="font-bold">${profile.followers_count || 0}</span> <span class="text-gray-600">Followers</span> </div> <div> <span class="font-bold">${profile.following_count || 0}</span> <span class="text-gray-600">Following</span> </div> </div> </div> <!-- Action Buttons --> <div class="flex space-x-2"> ${profile.user_id !== currentUser.id ? ` <button onclick="sendFriendRequest(${profile.user_id})" class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700"> ${profile.friendship_status === 'pending' ? 'Request Pending' : profile.friendship_status === 'accepted' ? 'Friends' : 'Add Friend'} </button> <button onclick="sendMessage(${profile.user_id})" class="bg-gray-200 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-300"> Message </button> ` : ` <button onclick="editProfile()" class="bg-gray-200 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-300"> Edit Profile </button> `} </div> </div> </div> <!-- User Posts --> <div class="border-t px-8 py-6"> <h3 class="text-lg font-semibold mb-4">Posts</h3> <div id="userPosts"> <!-- Posts loaded via JavaScript --> </div> </div> </div> `; // Load user posts loadUserPosts(profile.user_id); } // Load user posts function loadUserPosts(userId) { fetch(`api/posts.php?action=feed&user=${userId}`) .then(response => response.json()) .then(data => { if (data.success) { document.getElementById('userPosts').innerHTML = renderFeed(data.posts); } }); } // Load messages function loadMessages() { currentPage = 'messages'; showLoading(); fetch('api/messages.php?action=list') .then(response => response.json()) .then(data => { if (data.success) { displayMessages(data.conversations); } }) .finally(() => { hideLoading(); }); } // Display messages function displayMessages(conversations) { const content = document.getElementById('contentArea'); content.innerHTML = ` <div class="bg-white rounded-lg shadow h-[calc(100vh-200px)] flex"> <!-- Conversations List --> <div class="w-1/3 border-r overflow-y-auto"> <div class="p-4 border-b"> <h3 class="text-lg font-semibold">Messages</h3> </div> <div id="conversationsList" class="divide-y"> ${renderConversations(conversations)} </div> </div> <!-- Active Conversation --> <div class="flex-1 flex flex-col"> <div id="activeConversation" class="flex-1 flex items-center justify-center text-gray-500"> Select a conversation to start messaging </div> </div> </div> `; } // Render conversations function renderConversations(conversations) { if (!conversations || conversations.length === 0) { return '<p class="text-center text-gray-500 p-4">No conversations yet</p>'; } return conversations.map(conv => ` <div class="p-4 hover:bg-gray-50 cursor-pointer" onclick="openConversation(${conv.other_user_id})"> <div class="flex items-center space-x-3"> <img src="${conv.profile_pic || 'assets/images/default-avatar.png'}" alt="${conv.full_name}" class="w-12 h-12 rounded-full object-cover"> <div class="flex-1 min-w-0"> <div class="flex justify-between items-baseline"> <h4 class="font-semibold truncate">${conv.full_name}</h4> <span class="text-xs text-gray-500">${timeAgo(conv.last_message_time)}</span> </div> <p class="text-sm text-gray-600 truncate">${conv.last_message || ''}</p> </div> ${conv.unread_count > 0 ? ` <span class="bg-blue-600 text-white text-xs px-2 py-1 rounded-full">${conv.unread_count}</span> ` : ''} </div> </div> `).join(''); } // Open conversation function openConversation(userId) { showLoading(); fetch(`api/messages.php?action=conversation&user_id=${userId}`) .then(response => response.json()) .then(data => { if (data.success) { displayConversation(userId, data.messages); } }) .finally(() => { hideLoading(); }); } // Display conversation function displayConversation(userId, messages) { const conversationDiv = document.getElementById('activeConversation'); conversationDiv.innerHTML = ` <div class="flex flex-col h-full"> <!-- Messages --> <div id="messageList" class="flex-1 overflow-y-auto p-4 space-y-4"> ${renderMessages(messages)} </div> <!-- Message Input --> <div class="border-t p-4"> <div class="flex items-center space-x-2"> <input type="text" id="messageInput" placeholder="Type a message..." class="flex-1 px-4 py-2 border rounded-full focus:outline-none focus:ring-2 focus:ring-blue-500" onkeypress="if(event.key === 'Enter') sendMessage(${userId})"> <button onclick="sendMessage(${userId})" class="bg-blue-600 text-white p-2 rounded-full hover:bg-blue-700"> <i class="fas fa-paper-plane"></i> </button> </div> </div> </div> `; // Scroll to bottom const messageList = document.getElementById('messageList'); messageList.scrollTop = messageList.scrollHeight; } // Render messages function renderMessages(messages) { if (!messages || messages.length === 0) { return '<p class="text-center text-gray-500">No messages yet. Start the conversation!</p>'; } return messages.map(msg => ` <div class="flex ${msg.sender_id === currentUser.id ? 'justify-end' : 'justify-start'}"> <div class="max-w-[70%] ${msg.sender_id === currentUser.id ? 'message-bubble-sent' : 'message-bubble-received'} px-4 py-2"> <p class="text-sm">${msg.content}</p> <p class="text-xs mt-1 ${msg.sender_id === currentUser.id ? 'text-blue-100' : 'text-gray-500'}"> ${timeAgo(msg.created_at)} </p> </div> </div> `).join(''); } // Send message function sendMessage(receiverId) { const input = document.getElementById('messageInput'); const content = input.value.trim(); if (!content) return; fetch('api/messages.php?action=send', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ receiver_id: receiverId, content: content }) }) .then(response => response.json()) .then(data => { if (data.success) { input.value = ''; // Add message to list const messageList = document.getElementById('messageList'); messageList.innerHTML += ` <div class="flex justify-end"> <div class="max-w-[70%] message-bubble-sent px-4 py-2"> <p class="text-sm">${content}</p> <p class="text-xs mt-1 text-blue-100">Just now</p> </div> </div> `; messageList.scrollTop = messageList.scrollHeight; } }); } // Load notifications function loadNotifications() { currentPage = 'notifications'; showLoading(); fetch('api/notifications.php?action=list') .then(response => response.json()) .then(data => { if (data.success) { displayNotifications(data.notifications); updateNotificationBadge(0); } }) .finally(() => { hideLoading(); }); } // Display notifications function displayNotifications(notifications) { const content = document.getElementById('contentArea'); content.innerHTML = ` <div class="bg-white rounded-lg shadow"> <div class="p-4 border-b"> <h3 class="text-lg font-semibold">Notifications</h3> </div> <div class="divide-y"> ${renderNotifications(notifications)} </div> ${notifications.length === 0 ? ` <div class="p-8 text-center text-gray-500"> No notifications yet </div> ` : ''} </div> `; } // Render notifications function renderNotifications(notifications) { return notifications.map(notif => ` <div class="p-4 hover:bg-gray-50 ${notif.is_read ? '' : 'bg-blue-50'}" onclick="markNotificationRead(${notif.id})"> <div class="flex items-center space-x-3"> <img src="${notif.actor_pic || 'assets/images/default-avatar.png'}" alt="${notif.actor_name}" class="w-10 h-10 rounded-full object-cover"> <div class="flex-1"> <p class="text-sm"> <span class="font-semibold">${notif.actor_name}</span> ${getNotificationText(notif)} </p> <p class="text-xs text-gray-500 mt-1">${timeAgo(notif.created_at)}</p> </div> ${!notif.is_read ? '<span class="w-2 h-2 bg-blue-600 rounded-full"></span>' : ''} </div> </div> `).join(''); } // Get notification text based on type function getNotificationText(notification) { switch(notification.type) { case 'like': return 'liked your post'; case 'comment': return 'commented on your post'; case 'share': return 'shared your post'; case 'friend_request': return 'sent you a friend request'; case 'friend_accept': return 'accepted your friend request'; case 'mention': return 'mentioned you in a post'; case 'message': return 'sent you a message'; case 'follow': return 'started following you'; case 'birthday': return 'has a birthday today'; default: return 'interacted with you'; } } // Mark notification as read function markNotificationRead(notificationId) { fetch('api/notifications.php?action=mark_read', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ notification_id: notificationId }) }); } // Create post function createPost() { const content = document.getElementById('postContent').value; const visibility = document.getElementById('postVisibility').value; if (!content) { alert('Please enter some content'); return; } showLoading(); const formData = new FormData(); formData.append('content', content); formData.append('visibility', visibility); // Add media files const imageInput = document.getElementById('postImage'); if (imageInput.files.length > 0) { for (let file of imageInput.files) { formData.append('media[]', file); } } fetch('api/posts.php?action=create', { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { closeCreatePostModal(); loadHome(); } else { alert('Failed to create post'); } }) .finally(() => { hideLoading(); }); } // Like post function likePost(postId) { fetch('api/posts.php?action=like', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ post_id: postId }) }) .then(response => response.json()) .then(data => { if (data.success) { // Refresh post loadHome(); } }); } // Add comment function addComment(postId) { const input = document.getElementById(`comment-${postId}`); const content = input.value.trim(); if (!content) return; fetch('api/posts.php?action=comment', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ post_id: postId, content: content }) }) .then(response => response.json()) .then(data => { if (data.success) { input.value = ''; loadComments(postId); } }); } // Load comments function loadComments(postId) { fetch(`api/posts.php?action=comments&post_id=${postId}`) .then(response => response.json()) .then(data => { if (data.success) { displayComments(postId, data.comments); } }); } // Display comments function displayComments(postId, comments) { const commentsDiv = document.getElementById(`comments-${postId}`); const commentsList = document.getElementById(`comments-list-${postId}`); commentsDiv.classList.remove('hidden'); commentsList.innerHTML = comments.map(comment => ` <div class="flex items-start space-x-2"> <img src="${comment.profile_pic || 'assets/images/default-avatar.png'}" alt="${comment.username}" class="w-6 h-6 rounded-full object-cover"> <div class="flex-1 bg-gray-100 rounded-lg px-3 py-2"> <p class="text-xs font-semibold">${comment.full_name}</p> <p class="text-sm">${comment.content}</p> <p class="text-xs text-gray-500 mt-1">${timeAgo(comment.created_at)}</p> </div> </div> `).join(''); } // Send friend request function sendFriendRequest(userId) { fetch('api/users.php?action=friend_request', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ user_id: userId }) }) .then(response => response.json()) .then(data => { if (data.success) { alert('Friend request sent!'); } }); } // Search function search() { const query = document.getElementById('searchInput').value || document.getElementById('mobileSearchInput').value; if (!query) return; showLoading(); fetch(`api/search.php?q=${encodeURIComponent(query)}`) .then(response => response.json()) .then(data => { if (data.success) { displaySearchResults(data.results, query); } }) .finally(() => { hideLoading(); }); } // Display search results function displaySearchResults(results, query) { const content = document.getElementById('contentArea'); content.innerHTML = ` <div class="bg-white rounded-lg shadow"> <div class="p-4 border-b"> <h3 class="text-lg font-semibold">Search Results for "${query}"</h3> </div> <!-- Users Results --> ${results.users && results.users.length > 0 ? ` <div class="p-4 border-b"> <h4 class="font-semibold mb-3">People</h4> <div class="space-y-3"> ${results.users.map(user => ` <div class="flex items-center justify-between"> <div class="flex items-center space-x-3"> <img src="${user.profile_pic || 'assets/images/default-avatar.png'}" alt="${user.full_name}" class="w-10 h-10 rounded-full object-cover cursor-pointer" onclick="loadUserProfile(${user.id})"> <div> <p class="font-medium cursor-pointer hover:underline" onclick="loadUserProfile(${user.id})"> ${user.full_name} </p> <p class="text-sm text-gray-500">@${user.username}</p> </div> </div> <button onclick="sendFriendRequest(${user.id})" class="text-blue-600 hover:text-blue-800"> <i class="fas fa-user-plus"></i> </button> </div> `).join('')} </div> </div> ` : ''} <!-- Posts Results --> ${results.posts && results.posts.length > 0 ? ` <div class="p-4"> <h4 class="font-semibold mb-3">Posts</h4> <div class="space-y-4"> ${results.posts.map(post => ` <div class="border rounded-lg p-3"> <div class="flex items-center space-x-2 mb-2"> <img src="${post.profile_pic || 'assets/images/default-avatar.png'}" alt="${post.full_name}" class="w-6 h-6 rounded-full object-cover"> <span class="text-sm font-medium">${post.full_name}</span> </div> <p class="text-sm">${post.content}</p> </div> `).join('')} </div> </div> ` : ''} ${(!results.users || results.users.length === 0) && (!results.posts || results.posts.length === 0) ? ` <div class="p-8 text-center text-gray-500"> No results found </div> ` : ''} </div> `; } // Time ago helper function timeAgo(timestamp) { const date = new Date(timestamp); const now = new Date(); const seconds = Math.floor((now - date) / 1000); if (seconds < 60) return 'just now'; if (seconds < 3600) return Math.floor(seconds / 60) + ' minutes ago'; if (seconds < 86400) return Math.floor(seconds / 3600) + ' hours ago'; if (seconds < 604800) return Math.floor(seconds / 86400) + ' days ago'; return date.toLocaleDateString(); } // Initialize WebSocket for real-time updates function initializeWebSocket() { if (!currentUser) return; // WebSocket implementation would go here // For now, use polling startPolling(); } // Start polling for notifications function startPolling() { notificationInterval = setInterval(() => { if (currentUser) { fetch('api/notifications.php?action=count') .then(response => response.json()) .then(data => { if (data.success) { updateNotificationBadge(data.count); } }); } }, 30000); // Poll every 30 seconds } // Update notification badge function updateNotificationBadge(count) { const badge = document.getElementById('notificationBadge'); const mobileBadge = document.getElementById('mobileNotificationBadge'); if (count > 0) { badge.textContent = count; badge.classList.remove('hidden'); mobileBadge.textContent = count; mobileBadge.classList.remove('hidden'); } else { badge.classList.add('hidden'); mobileBadge.classList.add('hidden'); } } // UI Helpers function showLoading() { document.getElementById('loadingOverlay').classList.remove('hidden'); } function hideLoading() { document.getElementById('loadingOverlay').classList.add('hidden'); } function showLoginModal() { document.getElementById('loginModal').classList.add('flex'); } function showRegisterModal() { closeModals(); document.getElementById('registerModal').classList.add('flex'); } function closeModals() { document.getElementById('loginModal').classList.remove('flex'); document.getElementById('registerModal').classList.remove('flex'); document.getElementById('createPostModal').classList.remove('flex'); } function openCreatePostModal() { document.getElementById('createPostModal').classList.add('flex'); } function closeCreatePostModal() { document.getElementById('createPostModal').classList.remove('flex'); document.getElementById('postContent').value = ''; document.getElementById('mediaPreview').innerHTML = ''; document.getElementById('mediaPreview').classList.add('hidden'); } function toggleProfileDropdown() { document.getElementById('profileMenu').classList.toggle('hidden'); } function toggleMobileMenu() { document.getElementById('mobileMenu').classList.toggle('hidden'); } // Close dropdown when clicking outside document.addEventListener('click', function(event) { const dropdown = document.getElementById('profileMenu'); const button = event.target.closest('button'); if (!button && !dropdown.contains(event.target)) { dropdown.classList.add('hidden'); } }); </script> </body> </html>

PROJECT STRUCTURE

social-media-platform/ │ ├── index.html # Main application ├── config.php # Database configuration ├── helpers.php # Helper functions │ ├── classes/ │ ├── User.php # User management │ ├── Post.php # Post management │ ├── SocialRecommender.php # Recommendation engine │ ├── Message.php # Messaging system │ └── Notification.php # Notifications │ ├── api/ │ ├── auth.php # Authentication endpoints │ ├── users.php # User endpoints │ ├── posts.php # Post endpoints │ ├── recommendations.php # Recommendation endpoints │ ├── messages.php # Message endpoints │ ├── notifications.php # Notification endpoints │ └── search.php # Search endpoints │ ├── assets/ │ ├── images/ │ │ ├── default-avatar.png │ │ └── default-cover.jpg │ └── css/ │ └── style.css │ └── uploads/ ├── profiles/ ├── covers/ ├── posts/ └── messages/

FEATURES SUMMARY

✅ User Management

  • Registration & Login
  • Profile customization
  • Profile/cover photos
  • Privacy settings
  • Account verification

✅ Social Features

  • Friend requests & management
  • Follow system
  • Public/private accounts
  • Block/report users
  • Activity status

✅ Content Creation

  • Text posts
  • Photo/video uploads
  • Post visibility options
  • Hashtags & mentions
  • Location tagging

✅ Engagement

  • Likes with reactions
  • Comments & replies
  • Shares
  • Saved posts
  • Post moderation

✅ Recommendations

  • Personalized feed
  • Friend suggestions
  • Trending content
  • Hashtag discovery
  • Similar content

✅ Messaging

  • Real-time chat
  • Media sharing
  • Read receipts
  • Conversation management
  • Message history

✅ Notifications

  • Real-time alerts
  • Multiple notification types
  • Read/unread tracking
  • Push notifications ready

✅ Search

  • User search
  • Post search
  • Hashtag search
  • Advanced filters
  • Search history

✅ Stories

  • 24-hour content
  • Story views tracking
  • Media support
  • Story archives

This complete social media platform includes all core features of modern social networks with a sophisticated content-based recommendation system that personalizes the user experience based on their interests, interactions, and behavior patterns.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper