Introduction to the Project
The Social Media Mini Platform is a comprehensive, full-stack web application that simulates core features of modern social networking platforms. This system provides users with a complete social experience including user profiles, posts, friendships, messaging, notifications, and content interaction. Built with scalability and user experience in mind, this platform demonstrates essential social media functionality while maintaining clean code architecture and security best practices.
The application features role-based access control with three user types: Admin, Regular Users, and Moderators. Users can create posts, share media, connect with friends, send messages, and engage with content through likes and comments. The platform includes real-time notifications, privacy settings, and content moderation tools.
Key Features
Core Features
- User Authentication: Secure registration, login, email verification, password recovery
- User Profiles: Customizable profiles with avatars, cover photos, bio, and personal information
- Posts & Feeds: Create text posts, share images, view personalized news feeds
- Friend System: Send/receive friend requests, manage connections, suggest friends
- Messaging: Real-time private messaging between friends
- Notifications: Real-time alerts for friend requests, likes, comments, and messages
- Content Interaction: Like, comment, and share posts
- Privacy Controls: Public, friends-only, or private account settings
- Search: Search for users, posts, and hashtags
- Media Sharing: Upload and share images with posts
Advanced Features
- Hashtags: Clickable hashtags that link to related posts
- Mentions: Tag users in posts and comments using @username
- Timeline: Chronological or algorithmic feed sorting
- Story Feature: 24-hour temporary stories with images
- Saved Posts: Bookmark posts for later viewing
- Block/Mute: Block or mute users to control experience
- Report System: Report inappropriate content to moderators
- Trending Topics: Algorithm to identify popular hashtags and topics
- Activity Log: View personal activity history
- Data Export: Export personal data as JSON/CSV
User Roles
Admin:
- Full system access and configuration
- User management (suspend/ban accounts)
- Content moderation dashboard
- View system analytics and reports
- Manage reported content
- Platform settings configuration
Moderator:
- Review reported content
- Remove inappropriate posts/comments
- Warn or temporarily restrict users
- View moderation queue
- Escalate issues to admin
Regular User:
- Create and manage profile
- Post content and interact with others
- Connect with friends
- Send and receive messages
- Customize privacy settings
- Report inappropriate content
Technology Stack
- Frontend: HTML5, CSS3, JavaScript (ES6+), Bootstrap 5
- Backend: PHP 8.0+ (Core PHP with OOP MVC pattern)
- Database: MySQL 5.7+
- Real-time: AJAX polling for notifications (WebSocket ready)
- Additional Libraries:
- Bootstrap 5 for responsive UI
- Font Awesome for icons
- jQuery for AJAX operations
- Select2 for enhanced dropdowns
- Cropper.js for image cropping
- Emoji Mart for emoji picker
- Moment.js for time formatting
- PHPMailer for emails
- Intervention Image for image processing
Project File Structure
social-platform/ │ ├── assets/ │ ├── css/ │ │ ├── style.css │ │ ├── dashboard.css │ │ ├── profile.css │ │ ├── messenger.css │ │ └── responsive.css │ ├── js/ │ │ ├── main.js │ │ ├── feed.js │ │ ├── profile.js │ │ ├── messenger.js │ │ ├── notifications.js │ │ ├── search.js │ │ └── validation.js │ ├── images/ │ │ ├── avatars/ │ │ ├── covers/ │ │ ├── posts/ │ │ └── stories/ │ └── plugins/ │ ├── cropper/ │ ├── emoji-mart/ │ └── select2/ │ ├── includes/ │ ├── config.php │ ├── Database.php │ ├── functions.php │ ├── auth.php │ ├── User.php │ ├── Post.php │ ├── Friend.php │ ├── Message.php │ ├── Notification.php │ ├── Comment.php │ ├── Like.php │ ├── Hashtag.php │ ├── Story.php │ └── helpers/ │ ├── ImageHelper.php │ ├── TimeHelper.php │ └── SecurityHelper.php │ ├── admin/ │ ├── dashboard.php │ ├── users.php │ ├── posts.php │ ├── reports.php │ ├── moderation.php │ ├── analytics.php │ └── settings.php │ ├── moderator/ │ ├── dashboard.php │ ├── reports.php │ ├── flagged_content.php │ └── actions.php │ ├── user/ │ ├── feed.php │ ├── profile.php │ ├── edit_profile.php │ ├── friends.php │ ├── messages.php │ ├── notifications.php │ ├── settings.php │ ├── saved.php │ ├── create_post.php │ ├── post.php │ ├── search.php │ ├── hashtag.php │ └── activity.php │ ├── api/ │ ├── posts.php │ ├── comments.php │ ├── likes.php │ ├── friends.php │ ├── messages.php │ ├── notifications.php │ ├── search.php │ ├── upload.php │ └── notifications_check.php │ ├── uploads/ │ ├── avatars/ │ ├── covers/ │ ├── posts/ │ └── stories/ │ ├── vendor/ │ ├── index.php ├── login.php ├── register.php ├── forgot_password.php ├── reset_password.php ├── logout.php ├── verify.php ├── .env ├── .gitignore ├── composer.json ├── cron/ │ ├── cleanup_stories.php │ └── send_digest.php └── sql/ └── database.sql
Database Schema
File: sql/database.sql
-- Create Database
CREATE DATABASE IF NOT EXISTS `social_platform`;
USE `social_platform`;
-- Users Table
CREATE TABLE `users` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`username` VARCHAR(50) UNIQUE NOT NULL,
`email` VARCHAR(100) UNIQUE NOT NULL,
`password` VARCHAR(255) NOT NULL,
`first_name` VARCHAR(50) NOT NULL,
`last_name` VARCHAR(50) NOT NULL,
`bio` TEXT,
`website` VARCHAR(255),
`location` VARCHAR(100),
`birth_date` DATE,
`gender` ENUM('male', 'female', 'other', 'prefer_not_to_say') DEFAULT 'prefer_not_to_say',
`avatar` VARCHAR(255) DEFAULT 'default.jpg',
`cover_photo` VARCHAR(255) DEFAULT 'default-cover.jpg',
`role` ENUM('admin', 'moderator', 'user') DEFAULT 'user',
`account_type` ENUM('public', 'private') DEFAULT 'public',
`verified` BOOLEAN DEFAULT FALSE,
`status` ENUM('active', 'suspended', 'banned', 'deleted') DEFAULT 'active',
`email_verified` BOOLEAN DEFAULT FALSE,
`verification_token` VARCHAR(255),
`reset_token` VARCHAR(255),
`reset_expires` DATETIME,
`last_login` DATETIME,
`last_active` TIMESTAMP NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
INDEX `idx_username` (`username`),
INDEX `idx_email` (`email`),
INDEX `idx_status` (`status`),
FULLTEXT INDEX `idx_search` (`username`, `first_name`, `last_name`, `bio`)
);
-- User Settings Table
CREATE TABLE `user_settings` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) NOT NULL,
`email_notifications` BOOLEAN DEFAULT TRUE,
`push_notifications` BOOLEAN DEFAULT TRUE,
`friend_request_notify` BOOLEAN DEFAULT TRUE,
`message_notify` BOOLEAN DEFAULT TRUE,
`like_notify` BOOLEAN DEFAULT TRUE,
`comment_notify` BOOLEAN DEFAULT TRUE,
`mention_notify` BOOLEAN DEFAULT TRUE,
`theme` VARCHAR(20) DEFAULT 'light',
`language` VARCHAR(5) DEFAULT 'en',
`timezone` VARCHAR(50) DEFAULT 'UTC',
`privacy_profile` ENUM('public', 'friends', 'private') DEFAULT 'public',
`privacy_posts` ENUM('public', 'friends', 'private') DEFAULT 'public',
`privacy_friends` ENUM('public', 'friends', 'private') DEFAULT 'public',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
UNIQUE KEY `unique_user` (`user_id`)
);
-- Posts Table
CREATE TABLE `posts` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) NOT NULL,
`content` TEXT NOT NULL,
`type` ENUM('text', 'image', 'video', 'link') DEFAULT 'text',
`media_url` VARCHAR(255),
`media_type` VARCHAR(50),
`visibility` ENUM('public', 'friends', 'private') DEFAULT 'public',
`allow_comments` BOOLEAN DEFAULT TRUE,
`location` VARCHAR(255),
`tags` TEXT,
`mentions` TEXT,
`likes_count` INT DEFAULT 0,
`comments_count` INT DEFAULT 0,
`shares_count` INT DEFAULT 0,
`is_pinned` BOOLEAN DEFAULT FALSE,
`status` ENUM('active', 'hidden', 'reported', 'deleted') DEFAULT 'active',
`ip_address` VARCHAR(45),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
INDEX `idx_user` (`user_id`),
INDEX `idx_created` (`created_at`),
INDEX `idx_visibility` (`visibility`),
FULLTEXT INDEX `idx_content` (`content`, `tags`)
);
-- Post Media Table
CREATE TABLE `post_media` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`post_id` INT(11) NOT NULL,
`user_id` INT(11) NOT NULL,
`media_url` VARCHAR(255) NOT NULL,
`media_type` VARCHAR(50) NOT NULL,
`size` INT,
`width` INT,
`height` INT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`post_id`) REFERENCES `posts`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
);
-- Comments Table
CREATE TABLE `comments` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`post_id` INT(11) NOT NULL,
`user_id` INT(11) NOT NULL,
`parent_id` INT(11),
`content` TEXT NOT NULL,
`mentions` TEXT,
`likes_count` INT DEFAULT 0,
`status` ENUM('active', 'hidden', 'deleted') DEFAULT 'active',
`ip_address` VARCHAR(45),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
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`)
);
-- Likes Table
CREATE TABLE `likes` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) NOT NULL,
`post_id` INT(11),
`comment_id` INT(11),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`post_id`) REFERENCES `posts`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`comment_id`) REFERENCES `comments`(`id`) ON DELETE CASCADE,
UNIQUE KEY `unique_like` (`user_id`, `post_id`, `comment_id`),
INDEX `idx_user` (`user_id`),
INDEX `idx_post` (`post_id`)
);
-- Friendships Table
CREATE TABLE `friendships` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) NOT NULL,
`friend_id` INT(11) NOT NULL,
`status` ENUM('pending', 'accepted', 'blocked', 'declined') DEFAULT 'pending',
`action_user_id` INT(11),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
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`),
UNIQUE KEY `unique_friendship` (`user_id`, `friend_id`),
INDEX `idx_user` (`user_id`),
INDEX `idx_friend` (`friend_id`),
INDEX `idx_status` (`status`)
);
-- Messages Table
CREATE TABLE `messages` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`sender_id` INT(11) NOT NULL,
`receiver_id` INT(11) NOT NULL,
`content` TEXT NOT NULL,
`type` ENUM('text', 'image', 'file') DEFAULT 'text',
`media_url` VARCHAR(255),
`is_read` BOOLEAN DEFAULT FALSE,
`read_at` DATETIME,
`is_delivered` BOOLEAN DEFAULT FALSE,
`delivered_at` DATETIME,
`is_deleted_sender` BOOLEAN DEFAULT FALSE,
`is_deleted_receiver` BOOLEAN DEFAULT FALSE,
`ip_address` VARCHAR(45),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`sender_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`receiver_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
INDEX `idx_conversation` (`sender_id`, `receiver_id`),
INDEX `idx_created` (`created_at`),
INDEX `idx_read` (`is_read`)
);
-- Notifications Table
CREATE TABLE `notifications` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) NOT NULL,
`actor_id` INT(11),
`type` ENUM('friend_request', 'friend_accept', 'like', 'comment', 'mention', 'share', 'message') NOT NULL,
`target_id` INT(11),
`target_type` ENUM('post', 'comment', 'user', 'message'),
`content` TEXT,
`is_read` BOOLEAN DEFAULT FALSE,
`read_at` DATETIME,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`actor_id`) REFERENCES `users`(`id`) ON DELETE SET NULL,
INDEX `idx_user` (`user_id`),
INDEX `idx_read` (`is_read`),
INDEX `idx_created` (`created_at`)
);
-- Hashtags Table
CREATE TABLE `hashtags` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`tag` VARCHAR(100) UNIQUE NOT NULL,
`count` INT DEFAULT 0,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
INDEX `idx_tag` (`tag`),
INDEX `idx_count` (`count`)
);
-- Post Hashtags Junction Table
CREATE TABLE `post_hashtags` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`post_id` INT(11) NOT NULL,
`hashtag_id` INT(11) NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
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`)
);
-- Stories Table
CREATE TABLE `stories` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) NOT NULL,
`media_url` VARCHAR(255) NOT NULL,
`media_type` ENUM('image', 'video') NOT NULL,
`text` VARCHAR(255),
`text_color` VARCHAR(7) DEFAULT '#ffffff',
`background_color` VARCHAR(7) DEFAULT '#000000',
`font_size` INT DEFAULT 24,
`duration` INT DEFAULT 24, -- hours until expiry
`views_count` INT DEFAULT 0,
`status` ENUM('active', 'expired', 'deleted') DEFAULT 'active',
`expires_at` TIMESTAMP NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
INDEX `idx_user` (`user_id`),
INDEX `idx_expires` (`expires_at`)
);
-- Story Views Table
CREATE TABLE `story_views` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`story_id` INT(11) NOT NULL,
`user_id` INT(11) NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
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`)
);
-- Saved Posts Table
CREATE TABLE `saved_posts` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) NOT NULL,
`post_id` INT(11) NOT NULL,
`collection` VARCHAR(100) DEFAULT 'default',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`post_id`) REFERENCES `posts`(`id`) ON DELETE CASCADE,
UNIQUE KEY `unique_save` (`user_id`, `post_id`)
);
-- Reports Table
CREATE TABLE `reports` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`reporter_id` INT(11) NOT NULL,
`reported_user_id` INT(11),
`post_id` INT(11),
`comment_id` INT(11),
`reason` ENUM('spam', 'harassment', 'nudity', 'violence', 'hate_speech', 'false_information', 'other') NOT NULL,
`description` TEXT,
`status` ENUM('pending', 'reviewed', 'resolved', 'dismissed') DEFAULT 'pending',
`action_taken` VARCHAR(255),
`moderator_id` INT(11),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`resolved_at` DATETIME,
PRIMARY KEY (`id`),
FOREIGN KEY (`reporter_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`reported_user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL,
FOREIGN KEY (`post_id`) REFERENCES `posts`(`id`) ON DELETE SET NULL,
FOREIGN KEY (`comment_id`) REFERENCES `comments`(`id`) ON DELETE SET NULL,
FOREIGN KEY (`moderator_id`) REFERENCES `users`(`id`) ON DELETE SET NULL,
INDEX `idx_status` (`status`)
);
-- Blocks Table
CREATE TABLE `blocks` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) NOT NULL,
`blocked_user_id` INT(11) NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
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`)
);
-- Activity Logs Table
CREATE TABLE `activity_logs` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11),
`action` VARCHAR(100) NOT NULL,
`details` JSON,
`ip_address` VARCHAR(45),
`user_agent` TEXT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL,
INDEX `idx_user` (`user_id`),
INDEX `idx_created` (`created_at`)
);
-- System Settings Table
CREATE TABLE `system_settings` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`setting_key` VARCHAR(100) UNIQUE NOT NULL,
`setting_value` TEXT,
`description` TEXT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
);
-- Insert Default Admin
INSERT INTO `users` (`username`, `email`, `password`, `first_name`, `last_name`, `role`, `email_verified`)
VALUES ('admin', '[email protected]', '$2y$10$YourHashedPasswordHere', 'System', 'Administrator', 'admin', TRUE);
-- Insert Default System Settings
INSERT INTO `system_settings` (`setting_key`, `setting_value`, `description`) VALUES
('site_name', 'Social Platform', 'Website name'),
('site_description', 'Connect with friends and the world around you', 'Site description'),
('site_keywords', 'social network, friends, messaging', 'SEO keywords'),
('site_email', '[email protected]', 'Contact email'),
('allow_registration', '1', 'Allow new user registrations'),
('require_email_verification', '1', 'Require email verification'),
('post_char_limit', '5000', 'Maximum characters per post'),
('comment_char_limit', '1000', 'Maximum characters per comment'),
('max_image_size', '5', 'Maximum image size in MB'),
('allowed_image_types', 'jpg,jpeg,png,gif', 'Allowed image types'),
('posts_per_page', '10', 'Posts per page in feed'),
('trending_hashtag_limit', '10', 'Number of trending hashtags to show'),
('story_duration', '24', 'Story duration in hours'),
('enable_notifications', '1', 'Enable notifications'),
('enable_messaging', '1', 'Enable private messaging'),
('enable_stories', '1', 'Enable stories feature'),
('default_theme', 'light', 'Default theme'),
('maintenance_mode', '0', 'Maintenance mode status'),
('app_version', '1.0.0', 'Application version');
Core PHP Classes
Database Class
File: includes/Database.php
<?php
/**
* Database Class
* Handles all database connections and operations using PDO with singleton pattern
*/
class Database {
private static $instance = null;
private $connection;
private $statement;
private $host;
private $dbname;
private $username;
private $password;
/**
* Private constructor for singleton pattern
*/
private function __construct() {
$this->host = DB_HOST;
$this->dbname = DB_NAME;
$this->username = DB_USER;
$this->password = DB_PASS;
try {
$this->connection = new PDO(
"mysql:host={$this->host};dbname={$this->dbname};charset=utf8mb4",
$this->username,
$this->password,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false
]
);
} catch (PDOException $e) {
die("Database connection failed: " . $e->getMessage());
}
}
/**
* Get database instance (Singleton)
*/
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Prepare and execute query with parameters
*/
public function query($sql, $params = []) {
try {
$this->statement = $this->connection->prepare($sql);
$this->statement->execute($params);
return $this->statement;
} catch (PDOException $e) {
$this->logError($e->getMessage(), $sql, $params);
throw new Exception("Database query failed: " . $e->getMessage());
}
}
/**
* Get single row
*/
public function getRow($sql, $params = []) {
$result = $this->query($sql, $params);
return $result->fetch();
}
/**
* Get multiple rows
*/
public function getRows($sql, $params = []) {
$result = $this->query($sql, $params);
return $result->fetchAll();
}
/**
* Get single value
*/
public function getValue($sql, $params = []) {
$result = $this->query($sql, $params);
return $result->fetchColumn();
}
/**
* Insert data and return last insert ID
*/
public function insert($table, $data) {
$columns = implode(', ', array_keys($data));
$placeholders = ':' . implode(', :', array_keys($data));
$sql = "INSERT INTO {$table} ({$columns}) VALUES ({$placeholders})";
$this->query($sql, $data);
return $this->connection->lastInsertId();
}
/**
* Update data
*/
public function update($table, $data, $where, $whereParams = []) {
$set = [];
foreach (array_keys($data) as $column) {
$set[] = "{$column} = :{$column}";
}
$sql = "UPDATE {$table} SET " . implode(', ', $set) . " WHERE {$where}";
$params = array_merge($data, $whereParams);
return $this->query($sql, $params)->rowCount();
}
/**
* Delete data
*/
public function delete($table, $where, $params = []) {
$sql = "DELETE FROM {$table} WHERE {$where}";
return $this->query($sql, $params)->rowCount();
}
/**
* Begin transaction
*/
public function beginTransaction() {
return $this->connection->beginTransaction();
}
/**
* Commit transaction
*/
public function commit() {
return $this->connection->commit();
}
/**
* Rollback transaction
*/
public function rollback() {
return $this->connection->rollBack();
}
/**
* Get last insert ID
*/
public function lastInsertId() {
return $this->connection->lastInsertId();
}
/**
* Log database errors
*/
private function logError($message, $sql, $params) {
$logFile = __DIR__ . '/../logs/database.log';
$logDir = dirname($logFile);
if (!is_dir($logDir)) {
mkdir($logDir, 0755, true);
}
$timestamp = date('Y-m-d H:i:s');
$logMessage = "[{$timestamp}] Error: {$message}\n";
$logMessage .= "SQL: {$sql}\n";
$logMessage .= "Params: " . json_encode($params) . "\n";
$logMessage .= "------------------------\n";
file_put_contents($logFile, $logMessage, FILE_APPEND);
}
/**
* Prevent cloning of the instance
*/
private function __clone() {}
/**
* Prevent unserializing of the instance
*/
public function __wakeup() {}
}
?>
Configuration File
File: includes/config.php
<?php
/**
* Configuration File
* Loads environment variables and sets up constants
*/
// Start session if not started
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
// Load environment variables from .env file
function loadEnv($path) {
if (!file_exists($path)) {
return false;
}
$lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
if (strpos(trim($line), '#') === 0) {
continue;
}
list($name, $value) = explode('=', $line, 2);
$name = trim($name);
$value = trim($value);
if (!array_key_exists($name, $_ENV)) {
$_ENV[$name] = $value;
putenv(sprintf('%s=%s', $name, $value));
}
}
return true;
}
// Load environment variables
loadEnv(__DIR__ . '/../.env');
// Database Configuration
define('DB_HOST', getenv('DB_HOST') ?: 'localhost');
define('DB_NAME', getenv('DB_NAME') ?: 'social_platform');
define('DB_USER', getenv('DB_USER') ?: 'root');
define('DB_PASS', getenv('DB_PASS') ?: '');
// Application Configuration
define('SITE_NAME', getenv('SITE_NAME') ?: 'Social Platform');
define('SITE_URL', getenv('SITE_URL') ?: 'http://localhost/social-platform');
define('SITE_EMAIL', getenv('SITE_EMAIL') ?: '[email protected]');
define('APP_VERSION', getenv('APP_VERSION') ?: '1.0.0');
define('DEBUG_MODE', getenv('DEBUG_MODE') === 'true');
// Security Configuration
define('SESSION_TIMEOUT', getenv('SESSION_TIMEOUT') ?: 3600); // 1 hour
define('BCRYPT_ROUNDS', 12);
define('CSRF_TOKEN_NAME', 'csrf_token');
define('JWT_SECRET', getenv('JWT_SECRET') ?: 'your-secret-key-change-this');
// Upload Configuration
define('UPLOAD_DIR', __DIR__ . '/../uploads/');
define('MAX_FILE_SIZE', getenv('MAX_FILE_SIZE') ?: 5 * 1024 * 1024); // 5MB
define('ALLOWED_IMAGE_TYPES', ['jpg', 'jpeg', 'png', 'gif']);
// Pagination
define('POSTS_PER_PAGE', getenv('POSTS_PER_PAGE') ?: 10);
define('COMMENTS_PER_PAGE', getenv('COMMENTS_PER_PAGE') ?: 20);
define('FRIENDS_PER_PAGE', getenv('FRIENDS_PER_PAGE') ?: 20);
define('MESSAGES_PER_PAGE', getenv('MESSAGES_PER_PAGE') ?: 50);
// Content Limits
define('POST_CHAR_LIMIT', getenv('POST_CHAR_LIMIT') ?: 5000);
define('COMMENT_CHAR_LIMIT', getenv('COMMENT_CHAR_LIMIT') ?: 1000);
// Date/Time Configuration
date_default_timezone_set(getenv('TIMEZONE') ?: 'UTC');
define('DATE_FORMAT', 'Y-m-d');
define('TIME_FORMAT', 'H:i');
define('DATETIME_FORMAT', 'Y-m-d H:i:s');
// Feature Flags
define('ALLOW_REGISTRATION', getenv('ALLOW_REGISTRATION') === 'true');
define('REQUIRE_EMAIL_VERIFICATION', getenv('REQUIRE_EMAIL_VERIFICATION') === 'true');
define('ENABLE_MESSAGING', getenv('ENABLE_MESSAGING') === 'true');
define('ENABLE_STORIES', getenv('ENABLE_STORIES') === 'true');
define('ENABLE_NOTIFICATIONS', getenv('ENABLE_NOTIFICATIONS') === 'true');
// Error Reporting
if (DEBUG_MODE) {
error_reporting(E_ALL);
ini_set('display_errors', 1);
} else {
error_reporting(0);
ini_set('display_errors', 0);
}
// Include required files
require_once __DIR__ . '/Database.php';
require_once __DIR__ . '/functions.php';
require_once __DIR__ . '/auth.php';
require_once __DIR__ . '/User.php';
require_once __DIR__ . '/Post.php';
require_once __DIR__ . '/Friend.php';
require_once __DIR__ . '/Message.php';
require_once __DIR__ . '/Notification.php';
require_once __DIR__ . '/Comment.php';
require_once __DIR__ . '/Like.php';
require_once __DIR__ . '/Hashtag.php';
require_once __DIR__ . '/Story.php';
// Initialize database connection
$db = Database::getInstance();
// Load system settings
$settings = $db->getRows("SELECT setting_key, setting_value FROM system_settings");
foreach ($settings as $setting) {
define(strtoupper($setting['setting_key']), $setting['setting_value']);
}
// Set timezone for MySQL
$db->query("SET time_zone = ?", [date('P')]);
// Initialize current user if logged in
if (isset($_SESSION['user_id'])) {
$currentUser = $db->getRow("SELECT * FROM users WHERE id = ?", [$_SESSION['user_id']]);
// Update last active
$db->update('users', ['last_active' => date('Y-m-d H:i:s')], 'id = :id', ['id' => $_SESSION['user_id']]);
}
?>
Helper Functions
File: includes/functions.php
<?php
/**
* Helper Functions
* Common utility functions used throughout the application
*/
/**
* Sanitize input data
*/
function sanitize($input) {
if (is_array($input)) {
return array_map('sanitize', $input);
}
return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8');
}
/**
* Generate CSRF token
*/
function generateCSRFToken() {
if (!isset($_SESSION[CSRF_TOKEN_NAME])) {
$_SESSION[CSRF_TOKEN_NAME] = bin2hex(random_bytes(32));
}
return $_SESSION[CSRF_TOKEN_NAME];
}
/**
* Verify CSRF token
*/
function verifyCSRFToken($token) {
if (!isset($_SESSION[CSRF_TOKEN_NAME]) || $token !== $_SESSION[CSRF_TOKEN_NAME]) {
return false;
}
return true;
}
/**
* Redirect to URL
*/
function redirect($url) {
header("Location: " . SITE_URL . $url);
exit();
}
/**
* Get time ago string
*/
function timeAgo($datetime) {
$time = strtotime($datetime);
$now = time();
$diff = $now - $time;
if ($diff < 60) {
return $diff . ' seconds ago';
} elseif ($diff < 3600) {
return floor($diff / 60) . ' minutes ago';
} elseif ($diff < 86400) {
return floor($diff / 3600) . ' hours ago';
} elseif ($diff < 2592000) {
return floor($diff / 86400) . ' days ago';
} elseif ($diff < 31536000) {
return floor($diff / 2592000) . ' months ago';
} else {
return floor($diff / 31536000) . ' years ago';
}
}
/**
* Format date for display
*/
function formatDate($date, $format = null) {
if ($format === null) {
$format = DATE_FORMAT . ' ' . TIME_FORMAT;
}
if ($date instanceof DateTime) {
return $date->format($format);
}
return date($format, strtotime($date));
}
/**
* Get user avatar URL
*/
function getAvatarUrl($avatar) {
if ($avatar && file_exists(UPLOAD_DIR . 'avatars/' . $avatar)) {
return SITE_URL . '/uploads/avatars/' . $avatar;
}
return SITE_URL . '/assets/images/default-avatar.jpg';
}
/**
* Get cover photo URL
*/
function getCoverUrl($cover) {
if ($cover && file_exists(UPLOAD_DIR . 'covers/' . $cover)) {
return SITE_URL . '/uploads/covers/' . $cover;
}
return SITE_URL . '/assets/images/default-cover.jpg';
}
/**
* Get post media URL
*/
function getPostMediaUrl($media) {
return SITE_URL . '/uploads/posts/' . $media;
}
/**
* 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];
}
/**
* Linkify hashtags and mentions in content
*/
function linkifyContent($content) {
// Linkify hashtags
$content = preg_replace('/#(\w+)/', '<a href="' . SITE_URL . '/user/hashtag.php?tag=$1" class="hashtag">#$1</a>', $content);
// Linkify mentions
$content = preg_replace('/@(\w+)/', '<a href="' . SITE_URL . '/user/profile.php?username=$1" class="mention">@$1</a>', $content);
// Linkify URLs
$content = preg_replace('/(https?:\/\/[^\s]+)/', '<a href="$1" target="_blank" rel="nofollow">$1</a>', $content);
return $content;
}
/**
* Truncate text
*/
function truncateText($text, $length = 200, $suffix = '...') {
if (strlen($text) <= $length) {
return $text;
}
return substr($text, 0, $length) . $suffix;
}
/**
* Upload file
*/
function uploadFile($file, $targetDir, $allowedTypes = null) {
if ($allowedTypes === null) {
$allowedTypes = ALLOWED_IMAGE_TYPES;
}
// Check for errors
if ($file['error'] !== UPLOAD_ERR_OK) {
return ['success' => false, 'error' => 'Upload failed with error code: ' . $file['error']];
}
// Check file size
if ($file['size'] > MAX_FILE_SIZE) {
return ['success' => false, 'error' => 'File size exceeds limit of ' . (MAX_FILE_SIZE / 1024 / 1024) . 'MB'];
}
// Check file type
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($extension, $allowedTypes)) {
return ['success' => false, 'error' => 'File type not allowed. Allowed types: ' . implode(', ', $allowedTypes)];
}
// Generate unique filename
$filename = uniqid() . '_' . time() . '.' . $extension;
$targetPath = $targetDir . '/' . $filename;
// Create directory if not exists
if (!is_dir($targetDir)) {
mkdir($targetDir, 0755, true);
}
// Upload file
if (move_uploaded_file($file['tmp_name'], $targetPath)) {
// Get image dimensions if it's an image
$dimensions = null;
if (in_array($extension, ['jpg', 'jpeg', 'png', 'gif'])) {
list($width, $height) = getimagesize($targetPath);
$dimensions = ['width' => $width, 'height' => $height];
}
return [
'success' => true,
'filename' => $filename,
'original_name' => $file['name'],
'path' => $targetPath,
'url' => SITE_URL . '/uploads/' . basename($targetDir) . '/' . $filename,
'dimensions' => $dimensions
];
}
return ['success' => false, 'error' => 'Failed to move uploaded file'];
}
/**
* Send email
*/
function sendEmail($to, $subject, $template, $data = []) {
// Load email template
$templateFile = __DIR__ . "/../emails/{$template}.php";
if (!file_exists($templateFile)) {
logError("Email template not found: {$template}");
return false;
}
// Extract data for template
extract($data);
ob_start();
include $templateFile;
$message = ob_get_clean();
// Headers
$headers = [
'MIME-Version: 1.0',
'Content-type: text/html; charset=utf-8',
'From: ' . SITE_NAME . ' <' . SITE_EMAIL . '>',
'Reply-To: ' . SITE_EMAIL,
'X-Mailer: PHP/' . phpversion()
];
return mail($to, $subject, $message, implode("\r\n", $headers));
}
/**
* Log error
*/
function logError($message, $context = []) {
$logFile = __DIR__ . '/../logs/error.log';
$logDir = dirname($logFile);
if (!is_dir($logDir)) {
mkdir($logDir, 0755, true);
}
$timestamp = date('Y-m-d H:i:s');
$contextStr = !empty($context) ? ' ' . json_encode($context) : '';
$logMessage = "[{$timestamp}] {$message}{$contextStr}\n";
file_put_contents($logFile, $logMessage, FILE_APPEND);
}
/**
* Log activity
*/
function logActivity($userId, $action, $details = []) {
$db = Database::getInstance();
$db->insert('activity_logs', [
'user_id' => $userId,
'action' => $action,
'details' => json_encode($details),
'ip_address' => $_SERVER['REMOTE_ADDR'] ?? null,
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? null
]);
}
/**
* Get user IP address
*/
function getUserIP() {
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
return $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
return $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
return $_SERVER['REMOTE_ADDR'];
}
}
/**
* Generate random string
*/
function generateRandomString($length = 32) {
return bin2hex(random_bytes($length / 2));
}
/**
* Validate email
*/
function validateEmail($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL);
}
/**
* Validate username
*/
function validateUsername($username) {
return preg_match('/^[a-zA-Z0-9_]{3,20}$/', $username);
}
/**
* Check if user is online (active in last 5 minutes)
*/
function isOnline($lastActive) {
if (!$lastActive) {
return false;
}
$diff = time() - strtotime($lastActive);
return $diff < 300; // 5 minutes
}
/**
* Get online status badge
*/
function getOnlineStatusBadge($lastActive) {
if (isOnline($lastActive)) {
return '<span class="online-indicator" title="Online"><span class="dot"></span></span>';
}
return '<span class="offline-indicator" title="Offline"><span class="dot"></span></span>';
}
/**
* Get friendship status between two users
*/
function getFriendshipStatus($userId, $otherUserId) {
$db = Database::getInstance();
$friendship = $db->getRow(
"SELECT * FROM friendships WHERE (user_id = ? AND friend_id = ?) OR (user_id = ? AND friend_id = ?)",
[$userId, $otherUserId, $otherUserId, $userId]
);
if (!$friendship) {
return 'none';
}
if ($friendship['status'] == 'accepted') {
return 'friends';
}
if ($friendship['status'] == 'pending') {
if ($friendship['user_id'] == $userId) {
return 'pending_sent';
} else {
return 'pending_received';
}
}
if ($friendship['status'] == 'blocked') {
return 'blocked';
}
return $friendship['status'];
}
/**
* Check if user is blocked
*/
function isBlocked($userId, $otherUserId) {
$db = Database::getInstance();
$block = $db->getRow(
"SELECT * FROM blocks WHERE user_id = ? AND blocked_user_id = ?",
[$userId, $otherUserId]
);
return $block ? true : false;
}
/**
* Get unread notifications count
*/
function getUnreadNotificationsCount($userId) {
$db = Database::getInstance();
return $db->getValue(
"SELECT COUNT(*) FROM notifications WHERE user_id = ? AND is_read = 0",
[$userId]
);
}
/**
* Get unread messages count
*/
function getUnreadMessagesCount($userId) {
$db = Database::getInstance();
return $db->getValue(
"SELECT COUNT(*) FROM messages WHERE receiver_id = ? AND is_read = 0",
[$userId]
);
}
/**
* Get friend requests count
*/
function getFriendRequestsCount($userId) {
$db = Database::getInstance();
return $db->getValue(
"SELECT COUNT(*) FROM friendships WHERE friend_id = ? AND status = 'pending'",
[$userId]
);
}
/**
* Generate pagination
*/
function paginate($currentPage, $totalPages, $url) {
if ($totalPages <= 1) {
return '';
}
$html = '<nav aria-label="Page navigation"><ul class="pagination">';
// Previous button
if ($currentPage > 1) {
$html .= '<li class="page-item"><a class="page-link" href="' . $url . '&page=' . ($currentPage - 1) . '">Previous</a></li>';
} else {
$html .= '<li class="page-item disabled"><span class="page-link">Previous</span></li>';
}
// Page numbers
for ($i = 1; $i <= $totalPages; $i++) {
if ($i == $currentPage) {
$html .= '<li class="page-item active"><span class="page-link">' . $i . '</span></li>';
} else {
$html .= '<li class="page-item"><a class="page-link" href="' . $url . '&page=' . $i . '">' . $i . '</a></li>';
}
}
// Next button
if ($currentPage < $totalPages) {
$html .= '<li class="page-item"><a class="page-link" href="' . $url . '&page=' . ($currentPage + 1) . '">Next</a></li>';
} else {
$html .= '<li class="page-item disabled"><span class="page-link">Next</span></li>';
}
$html .= '</ul></nav>';
return $html;
}
/**
* Get file size in human readable format
*/
function humanFileSize($bytes, $decimals = 2) {
$size = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
$factor = floor((strlen($bytes) - 1) / 3);
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . ' ' . $size[$factor];
}
/**
* Create user settings if not exists
*/
function ensureUserSettings($userId) {
$db = Database::getInstance();
$exists = $db->getValue("SELECT COUNT(*) FROM user_settings WHERE user_id = ?", [$userId]);
if (!$exists) {
$db->insert('user_settings', ['user_id' => $userId]);
}
}
/**
* Get user setting
*/
function getUserSetting($userId, $key, $default = null) {
$db = Database::getInstance();
$setting = $db->getRow(
"SELECT * FROM user_settings WHERE user_id = ?",
[$userId]
);
if ($setting && isset($setting[$key])) {
return $setting[$key];
}
return $default;
}
?>
Authentication Class
File: includes/auth.php
<?php
/**
* Authentication Class
* Handles user authentication, registration, and session management
*/
class Auth {
private $db;
/**
* Constructor
*/
public function __construct() {
$this->db = Database::getInstance();
}
/**
* Register new user
*/
public function register($data) {
try {
// Check if username or email already exists
$existing = $this->db->getRow(
"SELECT id FROM users WHERE username = ? OR email = ?",
[$data['username'], $data['email']]
);
if ($existing) {
return ['success' => false, 'error' => 'Username or email already exists'];
}
// Hash password
$hashedPassword = password_hash($data['password'], PASSWORD_BCRYPT, ['cost' => BCRYPT_ROUNDS]);
// Generate verification token
$verificationToken = generateRandomString();
// Prepare user data
$userData = [
'username' => $data['username'],
'email' => $data['email'],
'password' => $hashedPassword,
'first_name' => $data['first_name'],
'last_name' => $data['last_name'],
'birth_date' => $data['birth_date'] ?? null,
'gender' => $data['gender'] ?? 'prefer_not_to_say',
'verification_token' => $verificationToken
];
// Insert user
$userId = $this->db->insert('users', $userData);
if ($userId) {
// Create user settings
$this->db->insert('user_settings', ['user_id' => $userId]);
// Send verification email if required
if (REQUIRE_EMAIL_VERIFICATION) {
$this->sendVerificationEmail($data['email'], $verificationToken);
}
// Log activity
logActivity($userId, 'register', ['email' => $data['email']]);
return [
'success' => true,
'user_id' => $userId,
'message' => REQUIRE_EMAIL_VERIFICATION ?
'Registration successful. Please check your email to verify your account.' :
'Registration successful. You can now login.'
];
}
return ['success' => false, 'error' => 'Registration failed'];
} catch (Exception $e) {
logError('Registration error: ' . $e->getMessage(), $data);
return ['success' => false, 'error' => 'Registration failed: ' . $e->getMessage()];
}
}
/**
* Login user
*/
public function login($login, $password, $remember = false) {
try {
// Get user by username or email
$user = $this->db->getRow(
"SELECT * FROM users WHERE (username = ? OR email = ?) AND status = 'active'",
[$login, $login]
);
if (!$user) {
return ['success' => false, 'error' => 'Invalid username/email or password'];
}
// Check if email verified
if (REQUIRE_EMAIL_VERIFICATION && !$user['email_verified']) {
return ['success' => false, 'error' => 'Please verify your email before logging in'];
}
// Verify password
if (!password_verify($password, $user['password'])) {
return ['success' => false, 'error' => 'Invalid username/email or password'];
}
// Check if password needs rehash
if (password_needs_rehash($user['password'], PASSWORD_BCRYPT, ['cost' => BCRYPT_ROUNDS])) {
$newHash = password_hash($password, PASSWORD_BCRYPT, ['cost' => BCRYPT_ROUNDS]);
$this->db->update('users', ['password' => $newHash], 'id = :id', ['id' => $user['id']]);
}
// Set session
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['user_role'] = $user['role'];
$_SESSION['user_name'] = $user['first_name'] . ' ' . $user['last_name'];
$_SESSION['logged_in'] = true;
$_SESSION['login_time'] = time();
// Update last login
$this->db->update(
'users',
['last_login' => date('Y-m-d H:i:s'), 'last_active' => date('Y-m-d H:i:s')],
'id = :id',
['id' => $user['id']]
);
// Set remember me cookie
if ($remember) {
$this->setRememberMe($user['id']);
}
// Log activity
logActivity($user['id'], 'login', ['ip' => getUserIP()]);
return ['success' => true, 'user' => $user];
} catch (Exception $e) {
logError('Login error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Login failed'];
}
}
/**
* Set remember me cookie
*/
private function setRememberMe($userId) {
$token = generateRandomString(64);
$expires = time() + (86400 * 30); // 30 days
// Store token in database (you would need a remember_tokens table)
// For now, just set cookie
setcookie('remember_token', $token, $expires, '/', '', false, true);
}
/**
* Logout user
*/
public function logout() {
if (isset($_SESSION['user_id'])) {
logActivity($_SESSION['user_id'], 'logout');
}
// Clear session
$_SESSION = array();
// Clear session cookie
if (ini_get('session.use_cookies')) {
$params = session_get_cookie_params();
setcookie(
session_name(),
'',
time() - 42000,
$params['path'],
$params['domain'],
$params['secure'],
$params['httponly']
);
}
// Clear remember me cookie
setcookie('remember_token', '', time() - 3600, '/');
// Destroy session
session_destroy();
}
/**
* Check if user is logged in
*/
public function isLoggedIn() {
return isset($_SESSION['logged_in']) && $_SESSION['logged_in'] === true;
}
/**
* Get current user
*/
public function getCurrentUser() {
if (!$this->isLoggedIn()) {
return null;
}
return $this->db->getRow(
"SELECT * FROM users WHERE id = ?",
[$_SESSION['user_id']]
);
}
/**
* Check if user has role
*/
public function hasRole($role) {
return isset($_SESSION['user_role']) && $_SESSION['user_role'] === $role;
}
/**
* Check if user has any of the roles
*/
public function hasAnyRole($roles) {
if (!isset($_SESSION['user_role'])) {
return false;
}
return in_array($_SESSION['user_role'], $roles);
}
/**
* Require login
*/
public function requireLogin() {
if (!$this->isLoggedIn()) {
$_SESSION['redirect_after_login'] = $_SERVER['REQUEST_URI'];
$_SESSION['error'] = 'Please login to access this page';
redirect('/login.php');
}
}
/**
* Require role
*/
public function requireRole($role) {
$this->requireLogin();
if (!$this->hasRole($role)) {
$_SESSION['error'] = 'You do not have permission to access this page';
// Redirect based on role
if ($this->hasRole('admin')) {
redirect('/admin/dashboard.php');
} elseif ($this->hasRole('moderator')) {
redirect('/moderator/dashboard.php');
} else {
redirect('/user/feed.php');
}
}
}
/**
* Verify email
*/
public function verifyEmail($token) {
$user = $this->db->getRow(
"SELECT id FROM users WHERE verification_token = ?",
[$token]
);
if ($user) {
$this->db->update(
'users',
['email_verified' => true, 'verification_token' => null],
'id = :id',
['id' => $user['id']]
);
logActivity($user['id'], 'verify_email');
return true;
}
return false;
}
/**
* Send verification email
*/
private function sendVerificationEmail($email, $token) {
$subject = "Verify your email - " . SITE_NAME;
$data = [
'verification_link' => SITE_URL . "/verify.php?token=" . $token
];
return sendEmail($email, $subject, 'verification', $data);
}
/**
* Forgot password
*/
public function forgotPassword($email) {
$user = $this->db->getRow(
"SELECT id, username FROM users WHERE email = ?",
[$email]
);
if ($user) {
$token = generateRandomString();
$expires = date('Y-m-d H:i:s', strtotime('+1 hour'));
$this->db->update(
'users',
['reset_token' => $token, 'reset_expires' => $expires],
'id = :id',
['id' => $user['id']]
);
// Send reset email
$subject = "Password Reset - " . SITE_NAME;
$data = [
'username' => $user['username'],
'reset_link' => SITE_URL . "/reset_password.php?token=" . $token
];
return sendEmail($email, $subject, 'password_reset', $data);
}
return false;
}
/**
* Reset password
*/
public function resetPassword($token, $password) {
$user = $this->db->getRow(
"SELECT id FROM users WHERE reset_token = ? AND reset_expires > NOW()",
[$token]
);
if ($user) {
$hashedPassword = password_hash($password, PASSWORD_BCRYPT, ['cost' => BCRYPT_ROUNDS]);
$this->db->update(
'users',
['password' => $hashedPassword, 'reset_token' => null, 'reset_expires' => null],
'id = :id',
['id' => $user['id']]
);
logActivity($user['id'], 'reset_password');
return true;
}
return false;
}
/**
* Change password
*/
public function changePassword($userId, $currentPassword, $newPassword) {
$user = $this->db->getRow("SELECT password FROM users WHERE id = ?", [$userId]);
if (!password_verify($currentPassword, $user['password'])) {
return ['success' => false, 'error' => 'Current password is incorrect'];
}
$hashedPassword = password_hash($newPassword, PASSWORD_BCRYPT, ['cost' => BCRYPT_ROUNDS]);
$this->db->update('users', ['password' => $hashedPassword], 'id = :id', ['id' => $userId]);
logActivity($userId, 'change_password');
return ['success' => true, 'message' => 'Password changed successfully'];
}
/**
* Check session timeout
*/
public function checkSessionTimeout() {
if ($this->isLoggedIn() && isset($_SESSION['login_time'])) {
if (time() - $_SESSION['login_time'] > SESSION_TIMEOUT) {
$this->logout();
$_SESSION['error'] = 'Your session has expired. Please login again.';
redirect('/login.php');
}
}
}
}
// Initialize Auth
$auth = new Auth();
// Check session timeout
$auth->checkSessionTimeout();
?>
User Class
File: includes/User.php
<?php
/**
* User Class
* Handles all user-related operations
*/
class User {
private $db;
/**
* Constructor
*/
public function __construct() {
$this->db = Database::getInstance();
}
/**
* Get user by ID
*/
public function getUserById($userId) {
return $this->db->getRow(
"SELECT * FROM users WHERE id = ?",
[$userId]
);
}
/**
* Get user by username
*/
public function getUserByUsername($username) {
return $this->db->getRow(
"SELECT * FROM users WHERE username = ?",
[$username]
);
}
/**
* Get user profile with additional data
*/
public function getUserProfile($userId, $currentUserId = null) {
$user = $this->getUserById($userId);
if (!$user) {
return null;
}
// Get posts count
$user['posts_count'] = $this->db->getValue(
"SELECT COUNT(*) FROM posts WHERE user_id = ? AND status = 'active'",
[$userId]
);
// Get friends count
$user['friends_count'] = $this->db->getValue(
"SELECT COUNT(*) FROM friendships WHERE (user_id = ? OR friend_id = ?) AND status = 'accepted'",
[$userId, $userId]
);
// Get followers count (if you implement followers system)
// Get following count
// Get friendship status with current user
if ($currentUserId && $currentUserId != $userId) {
$user['friendship_status'] = getFriendshipStatus($currentUserId, $userId);
$user['is_blocked'] = isBlocked($currentUserId, $userId);
$user['blocked_by'] = isBlocked($userId, $currentUserId);
}
// Get user settings
$user['settings'] = $this->db->getRow(
"SELECT * FROM user_settings WHERE user_id = ?",
[$userId]
);
return $user;
}
/**
* Update user profile
*/
public function updateProfile($userId, $data) {
try {
$updateData = [
'first_name' => $data['first_name'],
'last_name' => $data['last_name'],
'bio' => $data['bio'] ?? null,
'website' => $data['website'] ?? null,
'location' => $data['location'] ?? null,
'birth_date' => $data['birth_date'] ?? null,
'gender' => $data['gender'] ?? 'prefer_not_to_say'
];
// Handle avatar upload
if (isset($_FILES['avatar']) && $_FILES['avatar']['error'] == 0) {
$uploadDir = UPLOAD_DIR . 'avatars/';
$result = uploadFile($_FILES['avatar'], $uploadDir);
if ($result['success']) {
// Delete old avatar if not default
$oldAvatar = $this->db->getValue("SELECT avatar FROM users WHERE id = ?", [$userId]);
if ($oldAvatar && $oldAvatar != 'default.jpg') {
@unlink(UPLOAD_DIR . 'avatars/' . $oldAvatar);
}
$updateData['avatar'] = $result['filename'];
}
}
// Handle cover photo upload
if (isset($_FILES['cover_photo']) && $_FILES['cover_photo']['error'] == 0) {
$uploadDir = UPLOAD_DIR . 'covers/';
$result = uploadFile($_FILES['cover_photo'], $uploadDir);
if ($result['success']) {
// Delete old cover if not default
$oldCover = $this->db->getValue("SELECT cover_photo FROM users WHERE id = ?", [$userId]);
if ($oldCover && $oldCover != 'default-cover.jpg') {
@unlink(UPLOAD_DIR . 'covers/' . $oldCover);
}
$updateData['cover_photo'] = $result['filename'];
}
}
$this->db->update('users', $updateData, 'id = :id', ['id' => $userId]);
logActivity($userId, 'update_profile', $updateData);
return ['success' => true, 'message' => 'Profile updated successfully'];
} catch (Exception $e) {
logError('Update profile error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Failed to update profile'];
}
}
/**
* Update user settings
*/
public function updateSettings($userId, $data) {
try {
ensureUserSettings($userId);
$updateData = [
'email_notifications' => $data['email_notifications'] ?? 0,
'push_notifications' => $data['push_notifications'] ?? 0,
'friend_request_notify' => $data['friend_request_notify'] ?? 0,
'message_notify' => $data['message_notify'] ?? 0,
'like_notify' => $data['like_notify'] ?? 0,
'comment_notify' => $data['comment_notify'] ?? 0,
'mention_notify' => $data['mention_notify'] ?? 0,
'theme' => $data['theme'] ?? 'light',
'language' => $data['language'] ?? 'en',
'timezone' => $data['timezone'] ?? 'UTC',
'privacy_profile' => $data['privacy_profile'] ?? 'public',
'privacy_posts' => $data['privacy_posts'] ?? 'public',
'privacy_friends' => $data['privacy_friends'] ?? 'public'
];
$this->db->update('user_settings', $updateData, 'user_id = :user_id', ['user_id' => $userId]);
logActivity($userId, 'update_settings');
return ['success' => true, 'message' => 'Settings updated successfully'];
} catch (Exception $e) {
logError('Update settings error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Failed to update settings'];
}
}
/**
* Search users
*/
public function searchUsers($query, $currentUserId = null, $limit = 10, $offset = 0) {
$sql = "SELECT u.*,
(SELECT COUNT(*) FROM friendships WHERE (user_id = u.id OR friend_id = u.id) AND status = 'accepted') as friends_count
FROM users u
WHERE (u.username LIKE :query OR u.first_name LIKE :query OR u.last_name LIKE :query OR u.email LIKE :query)
AND u.status = 'active'
AND u.id != :current_id";
if ($currentUserId) {
$sql .= " AND u.id NOT IN (SELECT blocked_user_id FROM blocks WHERE user_id = :current_id2)";
}
$sql .= " ORDER BY u.created_at DESC LIMIT :limit OFFSET :offset";
$params = [
'query' => '%' . $query . '%',
'current_id' => $currentUserId,
'current_id2' => $currentUserId,
'limit' => $limit,
'offset' => $offset
];
return $this->db->getRows($sql, $params);
}
/**
* Get suggested friends
*/
public function getSuggestedFriends($userId, $limit = 10) {
// Get user's friends
$friends = $this->db->getRows(
"SELECT friend_id FROM friendships WHERE user_id = ? AND status = 'accepted'
UNION
SELECT user_id FROM friendships WHERE friend_id = ? AND status = 'accepted'",
[$userId, $userId]
);
$friendIds = [$userId];
foreach ($friends as $friend) {
$friendIds[] = $friend['friend_id'] ?? $friend['user_id'];
}
$placeholders = implode(',', array_fill(0, count($friendIds), '?'));
// Get friends of friends who are not already friends
$sql = "SELECT DISTINCT u.*,
(SELECT COUNT(*) FROM friendships WHERE (user_id = u.id OR friend_id = u.id) AND status = 'accepted') as friends_count,
(SELECT COUNT(*) FROM friendships WHERE (user_id = u.id AND friend_id IN ($placeholders)) OR (friend_id = u.id AND user_id IN ($placeholders))) as mutual_friends
FROM users u
WHERE u.id NOT IN ($placeholders)
AND u.status = 'active'
AND u.account_type = 'public'
ORDER BY mutual_friends DESC, u.created_at DESC
LIMIT ?";
$params = array_merge($friendIds, $friendIds, $friendIds, [$limit]);
return $this->db->getRows($sql, $params);
}
/**
* Block user
*/
public function blockUser($userId, $blockedUserId) {
try {
// Check if already blocked
$exists = $this->db->getRow(
"SELECT id FROM blocks WHERE user_id = ? AND blocked_user_id = ?",
[$userId, $blockedUserId]
);
if ($exists) {
return ['success' => false, 'error' => 'User already blocked'];
}
// Remove any friendship
$this->db->delete(
"friendships",
"(user_id = ? AND friend_id = ?) OR (user_id = ? AND friend_id = ?)",
[$userId, $blockedUserId, $blockedUserId, $userId]
);
// Add block
$this->db->insert('blocks', [
'user_id' => $userId,
'blocked_user_id' => $blockedUserId
]);
logActivity($userId, 'block_user', ['blocked_user_id' => $blockedUserId]);
return ['success' => true, 'message' => 'User blocked successfully'];
} catch (Exception $e) {
logError('Block user error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Failed to block user'];
}
}
/**
* Unblock user
*/
public function unblockUser($userId, $blockedUserId) {
try {
$deleted = $this->db->delete(
"blocks",
"user_id = ? AND blocked_user_id = ?",
[$userId, $blockedUserId]
);
if ($deleted) {
logActivity($userId, 'unblock_user', ['unblocked_user_id' => $blockedUserId]);
return ['success' => true, 'message' => 'User unblocked successfully'];
}
return ['success' => false, 'error' => 'User not found in block list'];
} catch (Exception $e) {
logError('Unblock user error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Failed to unblock user'];
}
}
/**
* Get blocked users
*/
public function getBlockedUsers($userId) {
return $this->db->getRows(
"SELECT u.* FROM blocks b
JOIN users u ON b.blocked_user_id = u.id
WHERE b.user_id = ?
ORDER BY b.created_at DESC",
[$userId]
);
}
/**
* Get user activity
*/
public function getUserActivity($userId, $limit = 20) {
return $this->db->getRows(
"SELECT * FROM activity_logs
WHERE user_id = ?
ORDER BY created_at DESC
LIMIT ?",
[$userId, $limit]
);
}
/**
* Deactivate account
*/
public function deactivateAccount($userId) {
try {
$this->db->update(
'users',
['status' => 'deleted'],
'id = :id',
['id' => $userId]
);
logActivity($userId, 'deactivate_account');
return ['success' => true, 'message' => 'Account deactivated successfully'];
} catch (Exception $e) {
logError('Deactivate account error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Failed to deactivate account'];
}
}
}
?>
Post Class
File: includes/Post.php
<?php
/**
* Post Class
* Handles all post-related operations
*/
class Post {
private $db;
/**
* Constructor
*/
public function __construct() {
$this->db = Database::getInstance();
}
/**
* Create new post
*/
public function create($userId, $data) {
try {
$this->db->beginTransaction();
// Extract hashtags
$hashtags = extractHashtags($data['content']);
// Extract mentions
$mentions = extractMentions($data['content']);
// Prepare post data
$postData = [
'user_id' => $userId,
'content' => $data['content'],
'type' => $data['type'] ?? 'text',
'visibility' => $data['visibility'] ?? 'public',
'allow_comments' => $data['allow_comments'] ?? 1,
'location' => $data['location'] ?? null,
'tags' => json_encode($hashtags),
'mentions' => json_encode($mentions),
'ip_address' => getUserIP()
];
// Handle media upload
if (isset($_FILES['media']) && $_FILES['media']['error'] == 0) {
$uploadDir = UPLOAD_DIR . 'posts/';
$result = uploadFile($_FILES['media'], $uploadDir);
if ($result['success']) {
$postData['media_url'] = $result['filename'];
$postData['media_type'] = $_FILES['media']['type'];
$postData['type'] = 'image';
}
}
// Insert post
$postId = $this->db->insert('posts', $postData);
if ($postId) {
// Process hashtags
$this->processHashtags($postId, $hashtags);
// Process mentions and create notifications
$this->processMentions($postId, $userId, $mentions);
// Log activity
logActivity($userId, 'create_post', ['post_id' => $postId]);
$this->db->commit();
return [
'success' => true,
'post_id' => $postId,
'message' => 'Post created successfully'
];
}
$this->db->rollback();
return ['success' => false, 'error' => 'Failed to create post'];
} catch (Exception $e) {
$this->db->rollback();
logError('Create post error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Failed to create post: ' . $e->getMessage()];
}
}
/**
* Get post by ID
*/
public function getPostById($postId, $currentUserId = null) {
$sql = "SELECT p.*,
u.username, u.first_name, u.last_name, u.avatar,
(SELECT COUNT(*) FROM 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 saved_posts WHERE post_id = p.id) as saves_count";
if ($currentUserId) {
$sql .= ",
(SELECT COUNT(*) FROM likes WHERE post_id = p.id AND user_id = :current_user_id) as user_liked,
(SELECT COUNT(*) FROM saved_posts WHERE post_id = p.id AND user_id = :current_user_id2) as user_saved";
}
$sql .= " FROM posts p
JOIN users u ON p.user_id = u.id
WHERE p.id = :post_id AND p.status = 'active'";
$params = ['post_id' => $postId];
if ($currentUserId) {
$params['current_user_id'] = $currentUserId;
$params['current_user_id2'] = $currentUserId;
}
return $this->db->getRow($sql, $params);
}
/**
* Get feed posts
*/
public function getFeedPosts($userId, $page = 1, $limit = null) {
if ($limit === null) {
$limit = POSTS_PER_PAGE;
}
$offset = ($page - 1) * $limit;
// Get user's friends
$friends = $this->db->getRows(
"SELECT friend_id FROM friendships WHERE user_id = ? AND status = 'accepted'
UNION
SELECT user_id FROM friendships WHERE friend_id = ? AND status = 'accepted'",
[$userId, $userId]
);
$friendIds = [$userId];
foreach ($friends as $friend) {
$friendIds[] = $friend['friend_id'] ?? $friend['user_id'];
}
$placeholders = implode(',', array_fill(0, count($friendIds), '?'));
// Get posts from user and friends
$sql = "SELECT p.*,
u.username, u.first_name, u.last_name, u.avatar,
(SELECT COUNT(*) FROM 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 likes WHERE post_id = p.id AND user_id = ?) as user_liked,
(SELECT COUNT(*) FROM saved_posts WHERE post_id = p.id AND user_id = ?) as user_saved
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, $userId], $friendIds, [$limit, $offset]);
return $this->db->getRows($sql, $params);
}
/**
* Get user posts
*/
public function getUserPosts($profileUserId, $currentUserId = null, $page = 1, $limit = null) {
if ($limit === null) {
$limit = POSTS_PER_PAGE;
}
$offset = ($page - 1) * $limit;
// Check privacy
$visibilityClause = "p.visibility IN ('public')";
if ($currentUserId && $currentUserId == $profileUserId) {
$visibilityClause = "1=1"; // Own posts - show all
} elseif ($currentUserId) {
$friendshipStatus = getFriendshipStatus($currentUserId, $profileUserId);
if ($friendshipStatus == 'friends') {
$visibilityClause = "p.visibility IN ('public', 'friends')";
}
}
$sql = "SELECT p.*,
u.username, u.first_name, u.last_name, u.avatar,
(SELECT COUNT(*) FROM 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 likes WHERE post_id = p.id AND user_id = ?) as user_liked,
(SELECT COUNT(*) FROM saved_posts WHERE post_id = p.id AND user_id = ?) as user_saved
FROM posts p
JOIN users u ON p.user_id = u.id
WHERE p.user_id = ?
AND p.status = 'active'
AND {$visibilityClause}
ORDER BY p.created_at DESC
LIMIT ? OFFSET ?";
$params = [$currentUserId, $currentUserId, $profileUserId, $limit, $offset];
return $this->db->getRows($sql, $params);
}
/**
* Update post
*/
public function updatePost($postId, $userId, $data) {
try {
// Check ownership
$post = $this->getPostById($postId);
if (!$post || $post['user_id'] != $userId) {
return ['success' => false, 'error' => 'Unauthorized'];
}
$updateData = [
'content' => $data['content'],
'visibility' => $data['visibility'] ?? $post['visibility'],
'allow_comments' => $data['allow_comments'] ?? $post['allow_comments'],
'location' => $data['location'] ?? $post['location']
];
// Extract new hashtags
$hashtags = extractHashtags($data['content']);
$this->db->beginTransaction();
$this->db->update('posts', $updateData, 'id = :id', ['id' => $postId]);
// Update hashtags
$this->db->delete('post_hashtags', 'post_id = ?', [$postId]);
$this->processHashtags($postId, $hashtags);
$this->db->commit();
logActivity($userId, 'update_post', ['post_id' => $postId]);
return ['success' => true, 'message' => 'Post updated successfully'];
} catch (Exception $e) {
$this->db->rollback();
logError('Update post error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Failed to update post'];
}
}
/**
* Delete post
*/
public function deletePost($postId, $userId) {
try {
// Check ownership or admin/moderator role
$post = $this->getPostById($postId);
if (!$post) {
return ['success' => false, 'error' => 'Post not found'];
}
$canDelete = ($post['user_id'] == $userId) ||
isset($_SESSION['user_role']) && in_array($_SESSION['user_role'], ['admin', 'moderator']);
if (!$canDelete) {
return ['success' => false, 'error' => 'Unauthorized'];
}
// Soft delete
$this->db->update('posts', ['status' => 'deleted'], 'id = :id', ['id' => $postId]);
logActivity($userId, 'delete_post', ['post_id' => $postId]);
return ['success' => true, 'message' => 'Post deleted successfully'];
} catch (Exception $e) {
logError('Delete post error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Failed to delete post'];
}
}
/**
* Process hashtags
*/
private function processHashtags($postId, $hashtags) {
foreach ($hashtags as $tag) {
// Get or create hashtag
$hashtag = $this->db->getRow("SELECT id FROM hashtags WHERE tag = ?", [$tag]);
if ($hashtag) {
$hashtagId = $hashtag['id'];
$this->db->update('hashtags', ['count' => $hashtag['count'] + 1], 'id = :id', ['id' => $hashtagId]);
} else {
$hashtagId = $this->db->insert('hashtags', ['tag' => $tag, 'count' => 1]);
}
// Link post to hashtag
$this->db->insert('post_hashtags', [
'post_id' => $postId,
'hashtag_id' => $hashtagId
]);
}
}
/**
* Process mentions and create notifications
*/
private function processMentions($postId, $userId, $mentions) {
$notification = new Notification();
foreach ($mentions as $username) {
$mentionedUser = $this->db->getRow("SELECT id FROM users WHERE username = ?", [$username]);
if ($mentionedUser && $mentionedUser['id'] != $userId) {
$notification->create(
$mentionedUser['id'],
$userId,
'mention',
'mentioned you in a post',
$postId,
'post'
);
}
}
}
/**
* Get posts by hashtag
*/
public function getPostsByHashtag($tag, $page = 1, $limit = null) {
if ($limit === null) {
$limit = POSTS_PER_PAGE;
}
$offset = ($page - 1) * $limit;
$sql = "SELECT p.*,
u.username, u.first_name, u.last_name, u.avatar,
(SELECT COUNT(*) FROM likes WHERE post_id = p.id) as likes_count,
(SELECT COUNT(*) FROM comments WHERE post_id = p.id AND status = 'active') as comments_count
FROM posts p
JOIN users u ON p.user_id = u.id
JOIN post_hashtags ph ON p.id = ph.post_id
JOIN hashtags h ON ph.hashtag_id = h.id
WHERE h.tag = ?
AND p.status = 'active'
AND p.visibility = 'public'
ORDER BY p.created_at DESC
LIMIT ? OFFSET ?";
return $this->db->getRows($sql, [$tag, $limit, $offset]);
}
/**
* Get trending hashtags
*/
public function getTrendingHashtags($limit = 10) {
return $this->db->getRows(
"SELECT * FROM hashtags
WHERE count > 0
ORDER BY count DESC, updated_at DESC
LIMIT ?",
[$limit]
);
}
/**
* Report post
*/
public function reportPost($postId, $reporterId, $reason, $description = null) {
try {
// Check if already reported
$existing = $this->db->getRow(
"SELECT id FROM reports WHERE post_id = ? AND reporter_id = ? AND status = 'pending'",
[$postId, $reporterId]
);
if ($existing) {
return ['success' => false, 'error' => 'You have already reported this post'];
}
$this->db->insert('reports', [
'reporter_id' => $reporterId,
'post_id' => $postId,
'reason' => $reason,
'description' => $description
]);
// Update post status to reported
$this->db->update('posts', ['status' => 'reported'], 'id = :id', ['id' => $postId]);
logActivity($reporterId, 'report_post', ['post_id' => $postId, 'reason' => $reason]);
return ['success' => true, 'message' => 'Post reported successfully'];
} catch (Exception $e) {
logError('Report post error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Failed to report post'];
}
}
}
?>
Friend Class
File: includes/Friend.php
<?php
/**
* Friend Class
* Handles all friend-related operations
*/
class Friend {
private $db;
/**
* Constructor
*/
public function __construct() {
$this->db = Database::getInstance();
}
/**
* Send friend request
*/
public function sendRequest($userId, $friendId) {
try {
// Check if users exist and are not the same
if ($userId == $friendId) {
return ['success' => false, 'error' => 'You cannot add yourself as a friend'];
}
// Check if already friends or request pending
$existing = $this->db->getRow(
"SELECT * FROM friendships WHERE (user_id = ? AND friend_id = ?) OR (user_id = ? AND friend_id = ?)",
[$userId, $friendId, $friendId, $userId]
);
if ($existing) {
if ($existing['status'] == 'accepted') {
return ['success' => false, 'error' => 'Already friends'];
} elseif ($existing['status'] == 'pending') {
return ['success' => false, 'error' => 'Friend request already sent'];
} elseif ($existing['status'] == 'blocked') {
return ['success' => false, 'error' => 'Cannot send friend request'];
}
}
// Check if blocked
if (isBlocked($userId, $friendId) || isBlocked($friendId, $userId)) {
return ['success' => false, 'error' => 'Cannot send friend request'];
}
// Send request
$this->db->insert('friendships', [
'user_id' => $userId,
'friend_id' => $friendId,
'status' => 'pending',
'action_user_id' => $userId
]);
// Create notification
$notification = new Notification();
$notification->create(
$friendId,
$userId,
'friend_request',
'sent you a friend request'
);
logActivity($userId, 'send_friend_request', ['friend_id' => $friendId]);
return ['success' => true, 'message' => 'Friend request sent'];
} catch (Exception $e) {
logError('Send friend request error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Failed to send friend request'];
}
}
/**
* Accept friend request
*/
public function acceptRequest($userId, $friendId) {
try {
$friendship = $this->db->getRow(
"SELECT * FROM friendships WHERE user_id = ? AND friend_id = ? AND status = 'pending'",
[$friendId, $userId]
);
if (!$friendship) {
return ['success' => false, 'error' => 'Friend request not found'];
}
// Update status
$this->db->update(
'friendships',
['status' => 'accepted', 'action_user_id' => $userId],
'id = :id',
['id' => $friendship['id']]
);
// Create notification
$notification = new Notification();
$notification->create(
$friendId,
$userId,
'friend_accept',
'accepted your friend request'
);
logActivity($userId, 'accept_friend_request', ['friend_id' => $friendId]);
return ['success' => true, 'message' => 'Friend request accepted'];
} catch (Exception $e) {
logError('Accept friend request error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Failed to accept friend request'];
}
}
/**
* Decline friend request
*/
public function declineRequest($userId, $friendId) {
try {
$deleted = $this->db->delete(
"friendships",
"user_id = ? AND friend_id = ? AND status = 'pending'",
[$friendId, $userId]
);
if ($deleted) {
logActivity($userId, 'decline_friend_request', ['friend_id' => $friendId]);
return ['success' => true, 'message' => 'Friend request declined'];
}
return ['success' => false, 'error' => 'Friend request not found'];
} catch (Exception $e) {
logError('Decline friend request error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Failed to decline friend request'];
}
}
/**
* Remove friend
*/
public function removeFriend($userId, $friendId) {
try {
$deleted = $this->db->delete(
"friendships",
"(user_id = ? AND friend_id = ?) OR (user_id = ? AND friend_id = ?)",
[$userId, $friendId, $friendId, $userId]
);
if ($deleted) {
logActivity($userId, 'remove_friend', ['friend_id' => $friendId]);
return ['success' => true, 'message' => 'Friend removed'];
}
return ['success' => false, 'error' => 'Friendship not found'];
} catch (Exception $e) {
logError('Remove friend error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Failed to remove friend'];
}
}
/**
* Get friends list
*/
public function getFriends($userId, $page = 1, $limit = null) {
if ($limit === null) {
$limit = FRIENDS_PER_PAGE;
}
$offset = ($page - 1) * $limit;
$sql = "SELECT u.*,
(SELECT COUNT(*) FROM messages WHERE (sender_id = u.id AND receiver_id = ?) OR (sender_id = ? AND receiver_id = u.id)) as messages_count
FROM users u
WHERE u.id IN (
SELECT friend_id FROM friendships WHERE user_id = ? AND status = 'accepted'
UNION
SELECT user_id FROM friendships WHERE friend_id = ? AND status = 'accepted'
)
AND u.status = 'active'
ORDER BY u.last_active DESC, u.first_name ASC
LIMIT ? OFFSET ?";
return $this->db->getRows($sql, [$userId, $userId, $userId, $userId, $limit, $offset]);
}
/**
* Get friend requests received
*/
public function getReceivedRequests($userId, $page = 1, $limit = null) {
if ($limit === null) {
$limit = FRIENDS_PER_PAGE;
}
$offset = ($page - 1) * $limit;
$sql = "SELECT u.*, f.created_at as request_date
FROM friendships f
JOIN users u ON f.user_id = u.id
WHERE f.friend_id = ? AND f.status = 'pending'
ORDER BY f.created_at DESC
LIMIT ? OFFSET ?";
return $this->db->getRows($sql, [$userId, $limit, $offset]);
}
/**
* Get friend requests sent
*/
public function getSentRequests($userId, $page = 1, $limit = null) {
if ($limit === null) {
$limit = FRIENDS_PER_PAGE;
}
$offset = ($page - 1) * $limit;
$sql = "SELECT u.*, f.created_at as request_date
FROM friendships f
JOIN users u ON f.friend_id = u.id
WHERE f.user_id = ? AND f.status = 'pending'
ORDER BY f.created_at DESC
LIMIT ? OFFSET ?";
return $this->db->getRows($sql, [$userId, $limit, $offset]);
}
/**
* Get mutual friends count
*/
public function getMutualFriendsCount($userId, $otherUserId) {
$sql = "SELECT COUNT(*) FROM (
SELECT friend_id FROM friendships WHERE user_id = ? AND status = 'accepted'
UNION
SELECT user_id FROM friendships WHERE friend_id = ? AND status = 'accepted'
) as user_friends
WHERE friend_id IN (
SELECT friend_id FROM friendships WHERE user_id = ? AND status = 'accepted'
UNION
SELECT user_id FROM friendships WHERE friend_id = ? AND status = 'accepted'
)";
return $this->db->getValue($sql, [$userId, $userId, $otherUserId, $otherUserId]);
}
/**
* Get friend suggestions
*/
public function getSuggestions($userId, $limit = 10) {
// Get user's friends
$friends = $this->getFriends($userId, 1, 100);
$friendIds = [$userId];
foreach ($friends as $friend) {
$friendIds[] = $friend['id'];
}
$placeholders = implode(',', array_fill(0, count($friendIds), '?'));
// Get friends of friends who are not already friends
$sql = "SELECT DISTINCT u.*,
(SELECT COUNT(*) FROM friendships WHERE (user_id = u.id OR friend_id = u.id) AND status = 'accepted') as friends_count,
(SELECT COUNT(*) FROM friendships WHERE (user_id = u.id AND friend_id IN ($placeholders)) OR (friend_id = u.id AND user_id IN ($placeholders))) as mutual_friends
FROM users u
WHERE u.id NOT IN ($placeholders)
AND u.status = 'active'
AND u.account_type = 'public'
ORDER BY mutual_friends DESC, u.created_at DESC
LIMIT ?";
$params = array_merge($friendIds, $friendIds, [$limit]);
return $this->db->getRows($sql, $params);
}
}
?>
Frontend Pages
Login Page
File: login.php
<?php
require_once 'includes/config.php';
// Redirect if already logged in
if ($auth->isLoggedIn()) {
redirect('/user/feed.php');
}
$error = '';
$success = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$login = sanitize($_POST['login'] ?? '');
$password = $_POST['password'] ?? '';
$remember = isset($_POST['remember']);
if (empty($login) || empty($password)) {
$error = 'Please enter username/email and password';
} else {
$result = $auth->login($login, $password, $remember);
if ($result['success']) {
// Redirect to requested page or feed
$redirect = $_SESSION['redirect_after_login'] ?? '/user/feed.php';
unset($_SESSION['redirect_after_login']);
redirect($redirect);
} else {
$error = $result['error'];
}
}
}
// Check for session messages
if (isset($_SESSION['success'])) {
$success = $_SESSION['success'];
unset($_SESSION['success']);
}
if (isset($_SESSION['error'])) {
$error = $_SESSION['error'];
unset($_SESSION['error']);
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - <?php echo SITE_NAME; ?></title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Custom CSS -->
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body class="bg-light">
<div class="container">
<div class="row justify-content-center align-items-center min-vh-100">
<div class="col-md-6 col-lg-5">
<div class="card shadow-lg border-0 rounded-lg">
<div class="card-header bg-primary text-white text-center py-4">
<h3 class="mb-0">
<i class="fas fa-users me-2"></i>
<?php echo SITE_NAME; ?>
</h3>
<p class="mb-0 text-white-50">Connect with friends and the world</p>
</div>
<div class="card-body p-5">
<h4 class="text-center mb-4">Welcome Back!</h4>
<?php if ($error): ?>
<div class="alert alert-danger alert-dismissible fade show">
<i class="fas fa-exclamation-circle me-2"></i>
<?php echo $error; ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<?php if ($success): ?>
<div class="alert alert-success alert-dismissible fade show">
<i class="fas fa-check-circle me-2"></i>
<?php echo $success; ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<form method="POST" action="" onsubmit="return validateLogin()">
<input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>">
<div class="mb-4">
<label for="login" class="form-label">
<i class="fas fa-user me-2"></i>Username or Email
</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-user"></i></span>
<input type="text" class="form-control" id="login" name="login"
placeholder="Enter username or email" required>
</div>
</div>
<div class="mb-4">
<label for="password" class="form-label">
<i class="fas fa-lock me-2"></i>Password
</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-lock"></i></span>
<input type="password" class="form-control" id="password" name="password"
placeholder="Enter password" required>
<button class="btn btn-outline-secondary" type="button" onclick="togglePassword()">
<i class="fas fa-eye" id="togglePasswordIcon"></i>
</button>
</div>
</div>
<div class="mb-4 form-check">
<input type="checkbox" class="form-check-input" id="remember" name="remember">
<label class="form-check-label" for="remember">Remember me</label>
<a href="forgot_password.php" class="float-end text-decoration-none">
Forgot Password?
</a>
</div>
<button type="submit" class="btn btn-primary w-100 py-2 mb-3">
<i class="fas fa-sign-in-alt me-2"></i>Login
</button>
</form>
<div class="text-center">
<p class="mb-0">
Don't have an account?
<a href="register.php" class="text-decoration-none">Sign up now</a>
</p>
</div>
<hr class="my-4">
<div class="text-center">
<a href="index.php" class="text-decoration-none">
<i class="fas fa-arrow-left me-1"></i>Back to Home
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<script>
function togglePassword() {
const password = document.getElementById('password');
const icon = document.getElementById('togglePasswordIcon');
if (password.type === 'password') {
password.type = 'text';
icon.classList.remove('fa-eye');
icon.classList.add('fa-eye-slash');
} else {
password.type = 'password';
icon.classList.remove('fa-eye-slash');
icon.classList.add('fa-eye');
}
}
function validateLogin() {
const login = document.getElementById('login').value.trim();
const password = document.getElementById('password').value;
if (login === '') {
alert('Please enter username or email');
return false;
}
if (password === '') {
alert('Please enter password');
return false;
}
return true;
}
</script>
<style>
body {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.card {
backdrop-filter: blur(10px);
background: rgba(255, 255, 255, 0.95);
}
</style>
</body>
</html>
Registration Page
File: register.php
<?php
require_once 'includes/config.php';
// Redirect if already logged in or registration disabled
if ($auth->isLoggedIn()) {
redirect('/user/feed.php');
}
if (!ALLOW_REGISTRATION) {
$_SESSION['error'] = 'Registration is currently disabled';
redirect('/login.php');
}
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Verify CSRF token
if (!verifyCSRFToken($_POST['csrf_token'] ?? '')) {
$error = 'Invalid security token';
} else {
// Sanitize inputs
$data = [
'username' => sanitize($_POST['username'] ?? ''),
'email' => sanitize($_POST['email'] ?? ''),
'password' => $_POST['password'] ?? '',
'confirm_password' => $_POST['confirm_password'] ?? '',
'first_name' => sanitize($_POST['first_name'] ?? ''),
'last_name' => sanitize($_POST['last_name'] ?? ''),
'birth_date' => $_POST['birth_date'] ?? null,
'gender' => $_POST['gender'] ?? 'prefer_not_to_say'
];
// Validate inputs
$errors = [];
if (empty($data['username'])) {
$errors[] = 'Username is required';
} elseif (!validateUsername($data['username'])) {
$errors[] = 'Username must be 3-20 characters and can only contain letters, numbers, and underscores';
}
if (empty($data['email'])) {
$errors[] = 'Email is required';
} elseif (!validateEmail($data['email'])) {
$errors[] = 'Please enter a valid email address';
}
if (empty($data['password'])) {
$errors[] = 'Password is required';
} elseif (strlen($data['password']) < 8) {
$errors[] = 'Password must be at least 8 characters';
} elseif (!preg_match('/[A-Z]/', $data['password'])) {
$errors[] = 'Password must contain at least one uppercase letter';
} elseif (!preg_match('/[a-z]/', $data['password'])) {
$errors[] = 'Password must contain at least one lowercase letter';
} elseif (!preg_match('/[0-9]/', $data['password'])) {
$errors[] = 'Password must contain at least one number';
}
if ($data['password'] !== $data['confirm_password']) {
$errors[] = 'Passwords do not match';
}
if (empty($data['first_name'])) {
$errors[] = 'First name is required';
}
if (empty($data['last_name'])) {
$errors[] = 'Last name is required';
}
// If no errors, attempt registration
if (empty($errors)) {
$result = $auth->register($data);
if ($result['success']) {
$_SESSION['success'] = $result['message'];
redirect('/login.php');
} else {
$error = $result['error'];
}
} else {
$error = implode('<br>', $errors);
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Register - <?php echo SITE_NAME; ?></title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Select2 CSS -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/select2.min.css" rel="stylesheet" />
<!-- Custom CSS -->
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body class="bg-light">
<div class="container">
<div class="row justify-content-center align-items-center min-vh-100 py-4">
<div class="col-md-8 col-lg-6">
<div class="card shadow-lg border-0 rounded-lg">
<div class="card-header bg-primary text-white text-center py-4">
<h3 class="mb-0">
<i class="fas fa-user-plus me-2"></i>
Join <?php echo SITE_NAME; ?>
</h3>
<p class="mb-0 text-white-50">Create your account and start connecting</p>
</div>
<div class="card-body p-5">
<?php if ($error): ?>
<div class="alert alert-danger alert-dismissible fade show">
<i class="fas fa-exclamation-circle me-2"></i>
<?php echo $error; ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<form method="POST" action="" onsubmit="return validateRegistration()">
<input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>">
<div class="row">
<div class="col-md-6 mb-3">
<label for="first_name" class="form-label">
<i class="fas fa-user me-2"></i>First Name *
</label>
<input type="text" class="form-control" id="first_name" name="first_name"
value="<?php echo htmlspecialchars($_POST['first_name'] ?? ''); ?>" required>
</div>
<div class="col-md-6 mb-3">
<label for="last_name" class="form-label">
<i class="fas fa-user me-2"></i>Last Name *
</label>
<input type="text" class="form-control" id="last_name" name="last_name"
value="<?php echo htmlspecialchars($_POST['last_name'] ?? ''); ?>" required>
</div>
</div>
<div class="mb-3">
<label for="username" class="form-label">
<i class="fas fa-at me-2"></i>Username *
</label>
<input type="text" class="form-control" id="username" name="username"
value="<?php echo htmlspecialchars($_POST['username'] ?? ''); ?>"
placeholder="Choose a username" required>
<small class="text-muted">3-20 characters, letters, numbers, and underscores only</small>
</div>
<div class="mb-3">
<label for="email" class="form-label">
<i class="fas fa-envelope me-2"></i>Email Address *
</label>
<input type="email" class="form-control" id="email" name="email"
value="<?php echo htmlspecialchars($_POST['email'] ?? ''); ?>"
placeholder="Enter your email" required>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="password" class="form-label">
<i class="fas fa-lock me-2"></i>Password *
</label>
<div class="input-group">
<input type="password" class="form-control" id="password" name="password"
placeholder="Create password" required>
<button class="btn btn-outline-secondary" type="button" onclick="togglePassword('password')">
<i class="fas fa-eye" id="togglePasswordIcon1"></i>
</button>
</div>
</div>
<div class="col-md-6 mb-3">
<label for="confirm_password" class="form-label">
<i class="fas fa-lock me-2"></i>Confirm Password *
</label>
<div class="input-group">
<input type="password" class="form-control" id="confirm_password" name="confirm_password"
placeholder="Confirm password" required>
<button class="btn btn-outline-secondary" type="button" onclick="togglePassword('confirm_password')">
<i class="fas fa-eye" id="togglePasswordIcon2"></i>
</button>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="birth_date" class="form-label">
<i class="fas fa-calendar me-2"></i>Date of Birth
</label>
<input type="date" class="form-control" id="birth_date" name="birth_date"
value="<?php echo htmlspecialchars($_POST['birth_date'] ?? ''); ?>">
</div>
<div class="col-md-6 mb-3">
<label for="gender" class="form-label">
<i class="fas fa-venus-mars me-2"></i>Gender
</label>
<select class="form-select" id="gender" name="gender">
<option value="prefer_not_to_say">Prefer not to say</option>
<option value="male">Male</option>
<option value="female">Female</option>
<option value="other">Other</option>
</select>
</div>
</div>
<div class="mb-4 form-check">
<input type="checkbox" class="form-check-input" id="terms" name="terms" required>
<label class="form-check-label" for="terms">
I agree to the <a href="#" target="_blank">Terms of Service</a> and
<a href="#" target="_blank">Privacy Policy</a>
</label>
</div>
<div class="mb-3">
<div class="password-requirements">
<small class="text-muted">
Password must contain:
<span id="req-length" class="req-badge">✓ 8+ characters</span>
<span id="req-uppercase" class="req-badge">✓ Uppercase</span>
<span id="req-lowercase" class="req-badge">✓ Lowercase</span>
<span id="req-number" class="req-badge">✓ Number</span>
</small>
</div>
</div>
<button type="submit" class="btn btn-primary w-100 py-2 mb-3">
<i class="fas fa-user-plus me-2"></i>Create Account
</button>
</form>
<div class="text-center">
<p class="mb-0">
Already have an account?
<a href="login.php" class="text-decoration-none">Sign In</a>
</p>
</div>
<hr class="my-4">
<div class="text-center">
<a href="index.php" class="text-decoration-none">
<i class="fas fa-arrow-left me-1"></i>Back to Home
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<script>
function togglePassword(fieldId) {
const password = document.getElementById(fieldId);
const icon = document.getElementById('togglePasswordIcon' + (fieldId === 'password' ? '1' : '2'));
if (password.type === 'password') {
password.type = 'text';
icon.classList.remove('fa-eye');
icon.classList.add('fa-eye-slash');
} else {
password.type = 'password';
icon.classList.remove('fa-eye-slash');
icon.classList.add('fa-eye');
}
}
function validateRegistration() {
const username = document.getElementById('username').value.trim();
const email = document.getElementById('email').value.trim();
const password = document.getElementById('password').value;
const confirm = document.getElementById('confirm_password').value;
const firstname = document.getElementById('first_name').value.trim();
const lastname = document.getElementById('last_name').value.trim();
const terms = document.getElementById('terms').checked;
// Check required fields
if (firstname === '' || lastname === '' || username === '' || email === '' || password === '') {
alert('Please fill in all required fields');
return false;
}
// Username validation
if (username.length < 3 || username.length > 20) {
alert('Username must be between 3 and 20 characters');
return false;
}
if (!/^[a-zA-Z0-9_]+$/.test(username)) {
alert('Username can only contain letters, numbers, and underscores');
return false;
}
// Email validation
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailPattern.test(email)) {
alert('Please enter a valid email address');
return false;
}
// Password validation
if (password.length < 8) {
alert('Password must be at least 8 characters');
return false;
}
if (!/[A-Z]/.test(password)) {
alert('Password must contain at least one uppercase letter');
return false;
}
if (!/[a-z]/.test(password)) {
alert('Password must contain at least one lowercase letter');
return false;
}
if (!/[0-9]/.test(password)) {
alert('Password must contain at least one number');
return false;
}
if (password !== confirm) {
alert('Passwords do not match');
return false;
}
if (!terms) {
alert('You must agree to the Terms of Service');
return false;
}
return true;
}
// Real-time password validation
document.getElementById('password').addEventListener('input', function() {
const password = this.value;
document.getElementById('req-length').style.color = password.length >= 8 ? 'green' : 'red';
document.getElementById('req-uppercase').style.color = /[A-Z]/.test(password) ? 'green' : 'red';
document.getElementById('req-lowercase').style.color = /[a-z]/.test(password) ? 'green' : 'red';
document.getElementById('req-number').style.color = /[0-9]/.test(password) ? 'green' : 'red';
});
</script>
<style>
body {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.card {
backdrop-filter: blur(10px);
background: rgba(255, 255, 255, 0.95);
}
.password-requirements {
background: #f8f9fa;
padding: 10px;
border-radius: 5px;
}
.req-badge {
display: inline-block;
margin-right: 10px;
font-size: 0.85rem;
}
</style>
</body>
</html>
User Feed Page
File: user/feed.php
<?php require_once '../includes/config.php'; require_once '../includes/auth.php'; // Require login $auth->requireLogin(); $userId = $_SESSION['user_id']; // Initialize classes $post = new Post(); $friend = new Friend(); $notification = new Notification(); // Get page number $page = isset($_GET['page']) ? (int)$_GET['page'] : 1; // Get feed posts $posts = $post->getFeedPosts($userId, $page); // Get friend suggestions $suggestions = $friend->getSuggestions($userId, 5); // Get trending hashtags $hashtags = $post->getTrendingHashtags(10); // Get unread counts $unreadNotifications = getUnreadNotificationsCount($userId); $unreadMessages = getUnreadMessagesCount($userId); $friendRequests = getFriendRequestsCount($userId); ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>News Feed - <?php echo SITE_NAME; ?></title> <!-- Bootstrap 5 CSS --> <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"> <!-- Font Awesome --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> <!-- Custom CSS --> <link rel="stylesheet" href="../assets/css/feed.css"> </head> <body> <!-- Navigation --> <nav class="navbar navbar-expand-lg navbar-dark bg-primary fixed-top"> <div class="container"> <a class="navbar-brand" href="feed.php"> <i class="fas fa-users me-2"></i> <?php echo SITE_NAME; ?> </a> <div class="collapse navbar-collapse" id="navbarNav"> <form class="d-flex mx-auto" style="width: 400px;" action="search.php" method="GET"> <div class="input-group"> <input class="form-control" type="search" name="q" placeholder="Search people, posts, or hashtags..." aria-label="Search"> <button class="btn btn-light" type="submit"> <i class="fas fa-search"></i> </button> </div> </form> <ul class="navbar-nav ms-auto"> <li class="nav-item"> <a class="nav-link" href="feed.php"> <i class="fas fa-home"></i> <span class="ms-1">Home</span> </a> </li> <li class="nav-item position-relative"> <a class="nav-link" href="friends.php"> <i class="fas fa-user-friends"></i> <span class="ms-1">Friends</span> <?php if ($friendRequests > 0): ?> <span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger"> <?php echo $friendRequests; ?> </span> <?php endif; ?> </a> </li> <li class="nav-item position-relative"> <a class="nav-link" href="messages.php"> <i class="fas fa-comment"></i> <span class="ms-1">Messages</span> <?php if ($unreadMessages > 0): ?> <span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger"> <?php echo $unreadMessages; ?> </span> <?php endif; ?> </a> </li> <li class="nav-item position-relative"> <a class="nav-link" href="notifications.php"> <i class="fas fa-bell"></i> <span class="ms-1">Notifications</span> <?php if ($unreadNotifications > 0): ?> <span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger"> <?php echo $unreadNotifications; ?> </span> <?php endif; ?> </a> </li> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" data-bs-toggle="dropdown"> <img src="<?php echo getAvatarUrl($_SESSION['avatar'] ?? 'default.jpg'); ?>" class="rounded-circle" width="30" height="30" alt=""> <span class="ms-1"><?php echo htmlspecialchars($_SESSION['user_name']); ?></span> </a> <ul class="dropdown-menu dropdown-menu-end"> <li> <a class="dropdown-item" href="profile.php?username=<?php echo $_SESSION['username']; ?>"> <i class="fas fa-user me-2"></i>My Profile </a> </li> <li> <a class="dropdown-item" href="settings.php"> <i class="fas fa-cog me-2"></i>Settings </a> </li> <li> <a class="dropdown-item" href="saved.php"> <i class="fas fa-bookmark me-2"></i>Saved Posts </a> </li> <li><hr class="dropdown-divider"></li> <li> <a class="dropdown-item text-danger" href="../logout.php"> <i class="fas fa-sign-out-alt me-2"></i>Logout </a> </li> </ul> </li> </ul> </div> </div> </nav> <!-- Main Content --> <div class="container mt-5 pt-4"> <div class="row"> <!-- Left Sidebar --> <div class="col-lg-3"> <div class="card mb-4"> <div class="card-body text-center"> <img src="<?php echo getAvatarUrl($_SESSION['avatar'] ?? 'default.jpg'); ?>" class="rounded-circle mb-3" width="80" height="80" alt=""> <h5><?php echo htmlspecialchars($_SESSION['user_name']); ?></h5> <p class="text-muted small">@<?php echo htmlspecialchars($_SESSION['username']); ?></p> <a href="profile.php?username=<?php echo $_SESSION['username']; ?>" class="btn btn-outline-primary btn-sm"> View Profile </a> </div> </div> <div class="card mb-4"> <div class="card-header"> <h6 class="mb-0">Trending Hashtags</h6> </div> <div class="card-body"> <?php if (count($hashtags) > 0): ?> <?php foreach ($hashtags as $tag): ?> <a href="hashtag.php?tag=<?php echo urlencode($tag['tag']); ?>" class="d-block mb-2 text-decoration-none"> <span class="text-primary">#<?php echo htmlspecialchars($tag['tag']); ?></span> <small class="text-muted float-end"><?php echo $tag['count']; ?> posts</small> </a> <?php endforeach; ?> <?php else: ?> <p class="text-muted mb-0">No trending hashtags</p> <?php endif; ?> </div> </div> </div> <!-- Main Feed --> <div class="col-lg-6"> <!-- Create Post --> <div class="card mb-4"> <div class="card-body"> <form action="create_post.php" method="POST" enctype="multipart/form-data"> <input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>"> <div class="mb-3"> <textarea class="form-control" name="content" rows="3" placeholder="What's on your mind, <?php echo htmlspecialchars($_SESSION['first_name']); ?>?" maxlength="<?php echo POST_CHAR_LIMIT; ?>" required></textarea> <small class="text-muted float-end"><span id="charCount">0</span>/<?php echo POST_CHAR_LIMIT; ?></small> </div> <div class="d-flex justify-content-between align-items-center"> <div> <input type="file" name="media" id="media" class="d-none" accept="image/*"> <button type="button" class="btn btn-outline-secondary btn-sm" onclick="document.getElementById('media').click();"> <i class="fas fa-image me-1"></i>Add Photo </button> <span id="fileName" class="ms-2 small text-muted"></span> </div> <div> <select class="form-select form-select-sm d-inline-block w-auto me-2" name="visibility"> <option value="public">🌍 Public</option> <option value="friends">👥 Friends</option> <option value="private">🔒 Only Me</option> </select> <button type="submit" class="btn btn-primary btn-sm"> <i class="fas fa-paper-plane me-1"></i>Post </button> </div> </div> </form> </div> </div> <!-- Posts --> <?php if (count($posts) > 0): ?> <?php foreach ($posts as $post): ?> <div class="card mb-3 post-card" id="post-<?php echo $post['id']; ?>"> <div class="card-body"> <!-- Post Header --> <div class="d-flex mb-3"> <img src="<?php echo getAvatarUrl($post['avatar']); ?>" class="rounded-circle me-3" width="50" height="50" alt=""> <div> <h6 class="mb-0"> <a href="profile.php?username=<?php echo $post['username']; ?>" class="text-decoration-none"> <?php echo htmlspecialchars($post['first_name'] . ' ' . $post['last_name']); ?> </a> </h6> <small class="text-muted"> @<?php echo $post['username']; ?> • <?php echo timeAgo($post['created_at']); ?> <?php if ($post['location']): ?> • <i class="fas fa-map-marker-alt"></i> <?php echo htmlspecialchars($post['location']); ?> <?php endif; ?> </small> </div> <?php if ($post['user_id'] == $userId || $_SESSION['user_role'] == 'admin'): ?> <div class="dropdown ms-auto"> <button class="btn btn-link text-dark p-0" type="button" data-bs-toggle="dropdown"> <i class="fas fa-ellipsis-h"></i> </button> <ul class="dropdown-menu"> <?php if ($post['user_id'] == $userId): ?> <li> <a class="dropdown-item" href="edit_post.php?id=<?php echo $post['id']; ?>"> <i class="fas fa-edit me-2"></i>Edit </a> </li> <?php endif; ?> <li> <a class="dropdown-item text-danger" href="#" onclick="deletePost(<?php echo $post['id']; ?>)"> <i class="fas fa-trash me-2"></i>Delete </a> </li> <li> <a class="dropdown-item" href="#" onclick="reportPost(<?php echo $post['id']; ?>)"> <i class="fas fa-flag me-2"></i>Report </a> </li> </ul> </div> <?php endif; ?> </div> <!-- Post Content --> <div class="post-content mb-3"> <?php echo linkifyContent(nl2br(htmlspecialchars($post['content']))); ?> </div> <!-- Post Media --> <?php if ($post['media_url']): ?> <div class="post-media mb-3"> <img src="<?php echo getPostMediaUrl($post['media_url']); ?>" class="img-fluid rounded" alt="Post image"> </div> <?php endif; ?> <!-- Post Stats --> <div class="d-flex justify-content-between mb-3 small text-muted"> <span> <i class="fas fa-heart me-1 <?php echo $post['user_liked'] ? 'text-danger' : ''; ?>"></i> <?php echo $post['likes_count']; ?> likes </span> <span> <i class="fas fa-comment me-1"></i> <?php echo $post['comments_count']; ?> comments </span> </div> <!-- Post Actions --> <div class="d-flex border-top pt-3"> <button class="btn btn-light flex-fill me-1 like-btn <?php echo $post['user_liked'] ? 'liked' : ''; ?>" onclick="toggleLike(<?php echo $post['id']; ?>)"> <i class="fas fa-heart me-1"></i> Like </button> <button class="btn btn-light flex-fill me-1" onclick="focusComment(<?php echo $post['id']; ?>)"> <i class="fas fa-comment me-1"></i> Comment </button> <button class="btn btn-light flex-fill save-btn <?php echo $post['user_saved'] ? 'saved' : ''; ?>" onclick="toggleSave(<?php echo $post['id']; ?>)"> <i class="fas fa-bookmark me-1"></i> Save </button> </div> <!-- Comments Section --> <div class="comments-section mt-3" id="comments-<?php echo $post['id']; ?>"> <!-- Comments will be loaded here via AJAX --> </div> <!-- Add Comment --> <div class="add-comment mt-3"> <div class="d-flex"> <img src="<?php echo getAvatarUrl($_SESSION['avatar'] ?? 'default.jpg'); ?>" class="rounded-circle me-2" width="32" height="32" alt=""> <div class="flex-grow-1"> <input type="text" class="form-control form-control-sm" placeholder="Write a comment..." onkeypress="if(event.keyCode==13) addComment(<?php echo $post['id']; ?>, this)"> </div> </div> </div> </div> </div> <?php endforeach; ?> <!-- Load More --> <div class="text-center mt-3"> <button class="btn btn-outline-primary" onclick="loadMore()"> Load More Posts </button> </div> <?php else: ?> <div class="card"> <div class="card-body text-center py-5"> <i class="fas fa-newspaper fa-4x text-muted mb-3"></i> <h5>No Posts Yet</h5> <p class="text-muted">Follow more people to see posts in your feed</p> <a href="search.php" class="btn btn-primary">Find Friends</a> </div> </div> <?php endif; ?> </div> <!-- Right Sidebar --> <div class="col-lg-3"> <!-- Friend Suggestions --> <div class="card mb-4"> <div class="card-header d-flex justify-content-between align-items-center"> <h6 class="mb-0">Friend Suggestions</h6> <a href="friends.php" class="small">See All</a> </div> <div class="card-body"> <?php if (count($suggestions) > 0): ?> <?php foreach ($suggestions as $suggestion): ?> <div class="d-flex align-items-center mb-3"> <img src="<?php getAvatarUrl($suggestion['avatar']); ?>" class="rounded-circle me-2" width="40" height="40" alt=""> <div class="flex-grow-1"> <a href="profile.php?username=<?php echo $suggestion['username']; ?>" class="text-decoration-none"> <strong><?php echo htmlspecialchars($suggestion['first_name'] . ' ' . $suggestion['last_name']); ?></strong> </a> <br> <small class="text-muted"> <?php echo $suggestion['mutual_friends']; ?> mutual friends </small> </div> <button class="btn btn-sm btn-outline-primary add-friend-btn" onclick="addFriend(<?php echo $suggestion['id']; ?>)"> <i class="fas fa-user-plus"></i> </button> </div> <?php endforeach; ?> <?php else: ?> <p class="text-muted mb-0">No suggestions</p> <?php endif; ?> </div> </div> <!-- Online Friends --> <div class="card"> <div class="card-header"> <h6 class="mb-0">Online Friends</h6> </div> <div class="card-body"> <p class="text-muted mb-0">Coming soon...</p> </div> </div> </div> </div> </div> <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <script> // Character counter document.querySelector('textarea[name="content"]').addEventListener('input', function() { document.getElementById('charCount').textContent = this.value.length; }); // File name display document.getElementById('media').addEventListener('change', function() { if (this.files.length > 0) { document.getElementById('fileName').textContent = this.files[0].name; } }); // Like post function toggleLike(postId) { $.post('../api/likes.php', { action: 'toggle', post_id: postId }, function(response) { if (response.success) { location.reload(); } }); } // Save post function toggleSave(postId) { $.post('../api/save.php', { action: 'toggle', post_id: postId }, function(response) { if (response.success) { location.reload(); } }); } // Add comment function addComment(postId, input) { if (input.value.trim() === '') return; $.post('../api/comments.php', { action: 'add', post_id: postId, content: input.value }, function(response) { if (response.success) { input.value = ''; loadComments(postId); } }); } // Load comments function loadComments(postId) { $.get('../api/comments.php', { post_id: postId }, function(response) { if (response.success) { $('#comments-' + postId).html(response.html); } }); } // Focus comment input function focusComment(postId) { $('#comments-' + postId).closest('.post-card').find('input[type="text"]').focus(); } // Delete post function deletePost(postId) { if (confirm('Are you sure you want to delete this post?')) { $.post('../api/posts.php', { action: 'delete', post_id: postId }, function(response) { if (response.success) { $('#post-' + postId).fadeOut(); } }); } } // Report post function reportPost(postId) { let reason = prompt('Please describe why you are reporting this post:'); if (reason) { $.post('../api/reports.php', { action: 'report', post_id: postId, reason: reason }, function(response) { if (response.success) { alert('Post reported successfully'); } }); } } // Add friend function addFriend(userId) { $.post('../api/friends.php', { action: 'send_request', user_id: userId }, function(response) { if (response.success) { $('.add-friend-btn[onclick*="' + userId + '"]') .removeClass('btn-outline-primary') .addClass('btn-outline-secondary') .prop('disabled', true) .html('<i class="fas fa-check"></i> Sent'); } }); } // Load more posts function loadMore() { let page = <?php echo $page + 1; ?>; window.location.href = '?page=' + page; } // Auto-refresh notifications every 30 seconds setInterval(function() { $.get('../api/notifications_check.php', function(response) { if (response.unread > 0) { // Update notification badge } }); }, 30000); </script> <style> body { padding-top: 70px; background: #f0f2f5; } .post-card { transition: all 0.3s ease; } .post-card:hover { box-shadow: 0 5px 15px rgba(0,0,0,0.1); } .like-btn.liked { color: #dc3545; } .like-btn.liked i { font-weight: 900; } .save-btn.saved { color: #0d6efd; } .save-btn.saved i { font-weight: 900; } </style> </body> </html>
Environment Configuration
File: .env
# Database Configuration DB_HOST=localhost DB_NAME=social_platform DB_USER=root DB_PASS= # Application Configuration SITE_NAME=Social Platform SITE_URL=http://localhost/social-platform [email protected] APP_VERSION=1.0.0 DEBUG_MODE=true # Security SESSION_TIMEOUT=3600 BCRYPT_ROUNDS=12 JWT_SECRET=your-secret-key-change-this # Upload Configuration MAX_FILE_SIZE=5242880 # 5MB in bytes # Pagination POSTS_PER_PAGE=10 COMMENTS_PER_PAGE=20 FRIENDS_PER_PAGE=20 MESSAGES_PER_PAGE=50 # Content Limits POST_CHAR_LIMIT=5000 COMMENT_CHAR_LIMIT=1000 # Date/Time TIMEZONE=America/New_York DATE_FORMAT=Y-m-d TIME_FORMAT=H:i # Feature Flags ALLOW_REGISTRATION=true REQUIRE_EMAIL_VERIFICATION=false ENABLE_MESSAGING=true ENABLE_STORIES=true ENABLE_NOTIFICATIONS=true
File: .gitignore
# Environment variables .env # Dependencies /vendor/ node_modules/ # IDE files .vscode/ .idea/ *.sublime-* # OS files .DS_Store Thumbs.db # Logs /logs/ *.log # Uploads /uploads/ !/uploads/.gitkeep !/uploads/avatars/.gitkeep !/uploads/covers/.gitkeep !/uploads/posts/.gitkeep # Cache /cache/ !/cache/.gitkeep # Composer composer.lock # Temp files *.tmp *.temp
File: composer.json
{
"name": "social-platform/application",
"description": "Social Media Mini Platform",
"type": "project",
"require": {
"php": ">=7.4",
"ext-pdo": "*",
"ext-json": "*",
"ext-mbstring": "*",
"ext-curl": "*",
"ext-gd": "*",
"phpmailer/phpmailer": "^6.8"
},
"require-dev": {
"phpunit/phpunit": "^9.0"
},
"autoload": {
"psr-4": {
"SocialPlatform\\": "src/"
}
},
"scripts": {
"test": "phpunit tests",
"post-install-cmd": [
"chmod -R 755 uploads/",
"chmod -R 755 logs/",
"chmod -R 755 cache/"
]
}
}
Cron Jobs
File: cron/cleanup_stories.php
<?php
/**
* Cron job to expire old stories
* Run every hour: 0 * * * * php /path/to/cron/cleanup_stories.php
*/
require_once __DIR__ . '/../includes/config.php';
$db = Database::getInstance();
// Expire stories
$db->update(
'stories',
['status' => 'expired'],
'expires_at < NOW() AND status = "active"'
);
echo "Stories cleaned up at " . date('Y-m-d H:i:s') . "\n";
File: cron/send_digest.php
<?php
/**
* Cron job to send daily digest emails
* Run daily at 8 AM: 0 8 * * * php /path/to/cron/send_digest.php
*/
require_once __DIR__ . '/../includes/config.php';
$db = Database::getInstance();
// Get users with email notifications enabled
$users = $db->getRows(
"SELECT u.* FROM users u
JOIN user_settings s ON u.id = s.user_id
WHERE u.status = 'active' AND s.email_notifications = 1"
);
foreach ($users as $user) {
// Get unread notifications count
$notifications = $db->getValue(
"SELECT COUNT(*) FROM notifications WHERE user_id = ? AND is_read = 0 AND created_at > DATE_SUB(NOW(), INTERVAL 24 HOUR)",
[$user['id']]
);
// Get new messages count
$messages = $db->getValue(
"SELECT COUNT(*) FROM messages WHERE receiver_id = ? AND is_read = 0 AND created_at > DATE_SUB(NOW(), INTERVAL 24 HOUR)",
[$user['id']]
);
// Get friend requests count
$requests = $db->getValue(
"SELECT COUNT(*) FROM friendships WHERE friend_id = ? AND status = 'pending' AND created_at > DATE_SUB(NOW(), INTERVAL 24 HOUR)",
[$user['id']]
);
if ($notifications > 0 || $messages > 0 || $requests > 0) {
$data = [
'name' => $user['first_name'],
'notifications' => $notifications,
'messages' => $messages,
'requests' => $requests
];
sendEmail($user['email'], 'Your Daily Digest - ' . SITE_NAME, 'daily_digest', $data);
}
}
echo "Digest emails sent at " . date('Y-m-d H:i:s') . "\n";
How to Use the Project (Step-by-Step Guide)
Prerequisites
- Web Server: XAMPP, WAMP, MAMP, or any PHP-enabled server (PHP 7.4+)
- Database: MySQL 5.7+ or MariaDB
- Composer: For dependency management
- Browser: Modern web browser (Chrome, Firefox, Edge, etc.)
Installation Steps
Step 1: Set Up Local Server
- Download and install XAMPP from https://www.apachefriends.org/
- Launch XAMPP Control Panel
- Start Apache and MySQL services
Step 2: Install Composer Dependencies
- Download and install Composer from https://getcomposer.org/
- Navigate to your project directory in terminal
- Run:
composer install
Step 3: Create Project Folder
- Navigate to
C:\xampp\htdocs\(Windows) or/Applications/XAMPP/htdocs/(Mac) - Create a new folder named
social-platform - Create all the folders and files as shown in the Project File Structure
Step 4: Set Up Database
- Open browser and go to
http://localhost/phpmyadmin - Click on "New" to create a new database
- Name the database
social_platformand selectutf8mb4_general_ci - Click on "Import" tab
- Click "Choose File" and select the
database.sqlfile from thesqlfolder - Click "Go" to import the database structure and sample data
Step 5: Configure Environment
- Rename
.env.exampleto.envin the project root - Update database credentials if different from default:
DB_HOST=localhost DB_NAME=social_platform DB_USER=root DB_PASS=
- Update application URL:
SITE_URL=http://localhost/social-platform
- Configure other settings as needed (feature flags, limits, etc.)
Step 6: Set Folder Permissions
Create the following folders and ensure they are writable:
uploads/avatars/uploads/covers/uploads/posts/logs/cache/
On Windows, right-click folders → Properties → Security → give Write permission to Users
On Mac/Linux, run: chmod -R 755 uploads/ logs/ cache/
Step 7: Create Admin User
- Go to
http://localhost/social-platform/register.php - Register a test user (e.g., username:
admin, email:[email protected], password:Admin@123) - Open phpMyAdmin, go to the
userstable - Find the user and change role to 'admin' and set
email_verified = 1
Step 8: Test the Installation
- Open browser and go to
http://localhost/social-platform/ - You should see the landing page
- Test different user types: Admin Login:
- Username:
adminor Email:[email protected] - Password:
Admin@123Regular User Registration: - Register as a new user
- Login and explore features
System Walkthrough
For Regular Users:
- Registration/Login - Create account or login
- News Feed - View posts from friends and followed users
- Create Post - Share text, images with privacy settings
- Profile - View and edit profile, see posts, friends, photos
- Friends - Search for friends, send/receive friend requests
- Messages - Chat with friends in real-time
- Notifications - View alerts for friend requests, likes, comments
- Search - Find users, posts, or hashtags
- Settings - Update profile, privacy, notification settings
For Admins:
- Admin Dashboard - View platform statistics
- User Management - View, edit, suspend, or ban users
- Post Management - Moderate reported posts
- Reports - View and resolve user reports
- System Settings - Configure platform settings
Key Features Explained
Creating Posts
- Type content in the post box on feed page
- Optionally add image
- Choose privacy (Public, Friends, Only Me)
- Click "Post" to share
- Use hashtags (#topic) and mentions (@username)
Friend System
- Search for users
- Click "Add Friend" to send request
- Accept/decline requests from notifications
- View friends list and online status
- See mutual friends
Messaging
- Click on Messages icon
- Select friend to chat with
- Send text messages
- Real-time updates via AJAX polling
- Unread message indicators
Notifications
- Real-time alerts for:
- Friend requests
- Friend request accepted
- Likes on posts
- Comments on posts
- Mentions (@username)
- New messages
Privacy Settings
- Public profile (visible to everyone)
- Friends-only content
- Private account (approve followers)
- Block users
- Control who can comment
Troubleshooting
Common Issues and Solutions
- Database Connection Error
- Check if MySQL is running
- Verify database credentials in
.env - Ensure database
social_platformexists
- 404 Page Not Found
- Check file paths and folder structure
- Verify
SITE_URLin.env - Ensure
.htaccessis properly configured (if using Apache)
- File Upload Issues
- Check folder permissions (uploads/ must be writable)
- Verify
MAX_FILE_SIZEin.env - Check allowed file types
- Email Not Sending
- Configure SMTP settings in PHPMailer
- Check spam folder
- Verify PHP mail() function is enabled
- AJAX Not Working
- Check browser console for errors
- Verify API endpoints exist
- Check CORS if using different domain
Security Best Practices
- Change default admin password immediately after installation
- Use HTTPS in production with SSL certificate
- Regular backups of database and uploads
- Input validation on both client and server side
- SQL injection prevention using prepared statements
- XSS prevention using
htmlspecialchars() - CSRF tokens for all forms
- Password hashing using bcrypt
- Rate limiting for login attempts
- Session security with proper timeout
- File upload validation and malware scanning
- Content moderation for reported posts
Performance Optimizations
- Database indexing on frequently queried columns
- Query caching for repeated requests
- Image optimization for uploaded photos
- Lazy loading for images and content
- Pagination for large datasets
- Minified CSS and JavaScript for production
- CDN for static assets
- Browser caching headers
- AJAX polling instead of WebSockets for simplicity
- Database query optimization
Deployment to Production
- Update
.envwith production settings - Set DEBUG_MODE=false
- Configure proper error logging
- Set up SSL certificate
- Configure cron jobs:
# Expire stories every hour 0 * * * * php /path/to/project/cron/cleanup_stories.php # Send daily digest at 8 AM 0 8 * * * php /path/to/project/cron/send_digest.php
- Set up database backups
- Configure CDN for static assets
- Enable caching headers
- Set up monitoring and alerts
Conclusion
The Social Media Mini Platform is a comprehensive, feature-rich application that demonstrates core social networking functionality. With its modular architecture, security best practices, and user-friendly interface, it provides a solid foundation for building modern social platforms.
This application demonstrates:
- Complete user authentication with email verification and password recovery
- User profiles with avatars, cover photos, and customizable information
- Post creation with text, images, hashtags, and mentions
- Friend system with requests, accept/decline, and suggestions
- Real-time messaging with read receipts and conversation history
- Notification system for all social interactions
- Content interaction with likes, comments, and saves
- Privacy controls and account settings
- Admin panel for moderation and system management
- Responsive design for all devices
- Security features including CSRF protection, input validation, and XSS prevention
The system is built to be extensible, allowing easy addition of new features such as:
- Stories (24-hour temporary content)
- Groups and communities
- Events and RSVPs
- Video sharing and live streaming
- Marketplace for buying/selling
- Advanced analytics and insights
- Mobile apps using the API
Whether you're building a niche community platform, learning social media architecture, or creating a foundation for a startup, this social media mini platform provides a robust, scalable, and secure starting point that can be customized and extended to meet specific requirements.