Recipe Sharing Website built with HTML, CSS, JavaScript, PHP, and MySQL

🍳 Project Introduction: Recipe Sharing Website

This project is a fully functional Recipe Sharing Platform where food enthusiasts can discover, share, and review recipes. Users can browse recipes by categories, search for specific dishes, submit their own recipes, rate and comment on recipes, and save favorites. Administrators have complete control over content moderation, user management, and site settings.

The Problem it Solves:
Food lovers often struggle to find reliable, well-organized recipes online. Many recipe websites are cluttered with ads and lack community features. This platform creates a clean, community-driven space where users can share their culinary creations, discover new dishes, and engage with other food enthusiasts through ratings and comments.

Key Features:

  • Public User Features:
  • Browse recipes by categories (Breakfast, Lunch, Dinner, Desserts, etc.)
  • Search recipes by name, ingredients, or cuisine
  • View detailed recipe pages with ingredients, instructions, prep time, and nutrition info
  • Filter recipes by dietary preferences (Vegetarian, Vegan, Gluten-Free, etc.)
  • Rate and review recipes
  • Save favorite recipes to personal collection
  • Submit own recipes with images
  • Create and manage personal profile
  • Follow other users
  • Print recipes
  • Share recipes on social media
  • Admin Features:
  • Dashboard with statistics (total recipes, users, comments)
  • Recipe moderation (approve/reject submitted recipes)
  • Category management
  • User management (view, edit, ban users)
  • Comment moderation
  • Featured recipes management
  • Site settings (site title, description, contact info)
  • Advertisement management
  • Newsletter system
  • Backup database

Technology Stack:

  • Frontend: HTML5, CSS3, JavaScript (Fetch API, Masonry layout)
  • Backend: PHP (Object-Oriented with PDO)
  • Database: MySQL
  • Additional Libraries:
  • TinyMCE (Rich text editor for recipe instructions)
  • Masonry (Pinterest-style grid layout)
  • Lightbox (Image gallery)
  • Chart.js (Admin analytics)

πŸ“ Project File Structure

recipe-sharing/
β”‚
β”œβ”€β”€ index.php                 # Homepage with featured recipes
β”œβ”€β”€ recipes.php               # Browse all recipes
β”œβ”€β”€ recipe.php                # Single recipe page
β”œβ”€β”€ category.php              # Recipes by category
β”œβ”€β”€ search.php                # Search results
β”œβ”€β”€ submit-recipe.php         # Submit new recipe
β”œβ”€β”€ edit-recipe.php           # Edit own recipe
β”œβ”€β”€ user-profile.php          # User profile page
β”œβ”€β”€ dashboard.php             # User dashboard (saved recipes)
β”œβ”€β”€ login.php                 # User login
β”œβ”€β”€ register.php              # User registration
β”œβ”€β”€ logout.php                # Logout script
β”‚
β”œβ”€β”€ admin/                    # Admin Panel
β”‚   β”œβ”€β”€ index.php             # Admin login
β”‚   β”œβ”€β”€ dashboard.php         # Admin dashboard
β”‚   β”œβ”€β”€ recipes.php           # Manage recipes
β”‚   β”œβ”€β”€ pending-recipes.php   # Moderate submitted recipes
β”‚   β”œβ”€β”€ edit-recipe.php       # Edit recipe (admin)
β”‚   β”œβ”€β”€ categories.php        # Manage categories
β”‚   β”œβ”€β”€ users.php             # Manage users
β”‚   β”œβ”€β”€ comments.php          # Moderate comments
β”‚   β”œβ”€β”€ featured.php          # Manage featured recipes
β”‚   β”œβ”€β”€ settings.php          # Site settings
β”‚   β”œβ”€β”€ newsletter.php        # Newsletter management
β”‚   └── logout.php            # Admin logout
β”‚
β”œβ”€β”€ includes/                  # Backend logic
β”‚   β”œβ”€β”€ config.php             # Database connection
β”‚   β”œβ”€β”€ functions.php          # Helper functions
β”‚   β”œβ”€β”€ auth.php               # Authentication functions
β”‚   β”œβ”€β”€ upload.php             # Image upload handling
β”‚   └── session.php            # Session management
β”‚
β”œβ”€β”€ api/                        # AJAX endpoints
β”‚   β”œβ”€β”€ get-recipes.php         # Infinite scroll
β”‚   β”œβ”€β”€ search-suggestions.php  # Live search
β”‚   β”œβ”€β”€ submit-rating.php       # Rate recipe
β”‚   β”œβ”€β”€ save-recipe.php         # Save to favorites
β”‚   └── upload-image.php        # AJAX image upload
β”‚
β”œβ”€β”€ assets/                    # Static assets
β”‚   β”œβ”€β”€ css/
β”‚   β”‚   β”œβ”€β”€ style.css          # Main styles
β”‚   β”‚   └── admin.css          # Admin styles
β”‚   β”œβ”€β”€ js/
β”‚   β”‚   β”œβ”€β”€ main.js            # Main JavaScript
β”‚   β”‚   β”œβ”€β”€ masonry.js         # Masonry layout
β”‚   β”‚   └── recipe-form.js     # Recipe submission form
β”‚   β”œβ”€β”€ images/
β”‚   β”‚   β”œβ”€β”€ recipes/           # Recipe images
β”‚   β”‚   β”œβ”€β”€ avatars/           # User avatars
β”‚   β”‚   └── uploads/           # Temporary uploads
β”‚   └── vendor/                 # Third-party libraries
β”‚       β”œβ”€β”€ tinymce/            # Rich text editor
β”‚       └── masonry/            # Masonry library
β”‚
└── database/
└── recipe_db.sql           # Database dump

πŸ—„οΈ Database Setup (database/recipe_db.sql)

Create a database named recipe_db and run this SQL.

-- phpMyAdmin SQL Dump
-- Database: `recipe_db`
CREATE DATABASE IF NOT EXISTS `recipe_db`;
USE `recipe_db`;
-- --------------------------------------------------------
-- Table structure for table `users`
-- --------------------------------------------------------
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`email` varchar(100) NOT NULL,
`password` varchar(255) NOT NULL,
`full_name` varchar(100) NOT NULL,
`bio` text DEFAULT NULL,
`avatar` varchar(255) DEFAULT 'default-avatar.png',
`role` enum('user','admin','moderator') DEFAULT 'user',
`status` enum('active','inactive','banned') DEFAULT 'active',
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
`last_login` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Default admin (password: admin123)
INSERT INTO `users` (`username`, `email`, `password`, `full_name`, `role`) VALUES
('admin', '[email protected]', '$2y$10$YourHashedPasswordHere', 'Administrator', 'admin');
-- Sample user (password: user123)
INSERT INTO `users` (`username`, `email`, `password`, `full_name`) VALUES
('johndoe', '[email protected]', '$2y$10$YourHashedPasswordHere', 'John Doe');
-- --------------------------------------------------------
-- Table structure for table `categories`
-- --------------------------------------------------------
CREATE TABLE `categories` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`slug` varchar(50) NOT NULL,
`description` text DEFAULT NULL,
`image` varchar(255) DEFAULT NULL,
`icon` varchar(50) DEFAULT '🍽️',
`color` varchar(20) DEFAULT '#3498db',
`menu_order` int(11) DEFAULT 0,
`status` enum('active','inactive') DEFAULT 'active',
PRIMARY KEY (`id`),
UNIQUE KEY `slug` (`slug`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Sample categories
INSERT INTO `categories` (`name`, `slug`, `icon`, `color`, `menu_order`) VALUES
('Breakfast', 'breakfast', '🍳', '#e67e22', 1),
('Lunch', 'lunch', 'πŸ₯ͺ', '#27ae60', 2),
('Dinner', 'dinner', '🍽️', '#8e44ad', 3),
('Desserts', 'desserts', '🍰', '#e74c3c', 4),
('Appetizers', 'appetizers', 'πŸ₯—', '#f39c12', 5),
('Soups', 'soups', 'πŸ₯£', '#3498db', 6),
('Salads', 'salads', 'πŸ₯—', '#2ecc71', 7),
('Beverages', 'beverages', 'πŸ₯€', '#9b59b6', 8);
-- --------------------------------------------------------
-- Table structure for table `cuisines`
-- --------------------------------------------------------
CREATE TABLE `cuisines` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`slug` varchar(50) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `slug` (`slug`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `cuisines` (`name`, `slug`) VALUES
('Italian', 'italian'),
('Chinese', 'chinese'),
('Mexican', 'mexican'),
('Indian', 'indian'),
('Japanese', 'japanese'),
('Thai', 'thai'),
('French', 'french'),
('Mediterranean', 'mediterranean');
-- --------------------------------------------------------
-- Table structure for table `dietary_options`
-- --------------------------------------------------------
CREATE TABLE `dietary_options` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`slug` varchar(50) NOT NULL,
`icon` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `slug` (`slug`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `dietary_options` (`name`, `slug`, `icon`) VALUES
('Vegetarian', 'vegetarian', 'πŸ₯•'),
('Vegan', 'vegan', '🌱'),
('Gluten-Free', 'gluten-free', '🌾'),
('Dairy-Free', 'dairy-free', 'πŸ₯›'),
('Keto', 'keto', 'πŸ₯‘'),
('Paleo', 'paleo', 'πŸ–'),
('Low-Carb', 'low-carb', 'πŸ₯¬'),
('Nut-Free', 'nut-free', 'πŸ₯œ');
-- --------------------------------------------------------
-- Table structure for table `recipes`
-- --------------------------------------------------------
CREATE TABLE `recipes` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(200) NOT NULL,
`slug` varchar(200) NOT NULL,
`description` text NOT NULL,
`ingredients` text NOT NULL,
`instructions` text NOT NULL,
`prep_time` int(11) DEFAULT NULL COMMENT 'in minutes',
`cook_time` int(11) DEFAULT NULL COMMENT 'in minutes',
`total_time` int(11) DEFAULT NULL COMMENT 'in minutes',
`servings` int(11) DEFAULT NULL,
`difficulty` enum('easy','medium','hard') DEFAULT 'medium',
`calories` int(11) DEFAULT NULL,
`featured_image` varchar(255) DEFAULT NULL,
`gallery_images` text DEFAULT NULL COMMENT 'JSON array',
`video_url` varchar(255) DEFAULT NULL,
`category_id` int(11) NOT NULL,
`cuisine_id` int(11) DEFAULT NULL,
`author_id` int(11) NOT NULL,
`views` int(11) DEFAULT 0,
`avg_rating` decimal(3,2) DEFAULT 0.00,
`rating_count` int(11) DEFAULT 0,
`status` enum('pending','approved','rejected','featured') DEFAULT 'pending',
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `slug` (`slug`),
KEY `category_id` (`category_id`),
KEY `cuisine_id` (`cuisine_id`),
KEY `author_id` (`author_id`),
KEY `status` (`status`),
FULLTEXT KEY `search` (`title`,`description`,`ingredients`),
CONSTRAINT `recipes_ibfk_1` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE CASCADE,
CONSTRAINT `recipes_ibfk_2` FOREIGN KEY (`cuisine_id`) REFERENCES `cuisines` (`id`) ON DELETE SET NULL,
CONSTRAINT `recipes_ibfk_3` FOREIGN KEY (`author_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Sample recipe
INSERT INTO `recipes` (`title`, `slug`, `description`, `ingredients`, `instructions`, `prep_time`, `cook_time`, `servings`, `difficulty`, `category_id`, `cuisine_id`, `author_id`, `status`) VALUES
('Classic Spaghetti Carbonara', 'classic-spaghetti-carbonara', 'A creamy, authentic Italian pasta dish made with eggs, cheese, pancetta, and black pepper.', '400g spaghetti\n200g pancetta or guanciale, diced\n4 large eggs\n100g Pecorino Romano cheese, grated\n100g Parmesan cheese, grated\nFreshly ground black pepper\nSalt', '1. Bring a large pot of salted water to boil and cook spaghetti according to package instructions.\n2. While pasta cooks, fry pancetta in a large pan until crispy.\n3. In a bowl, whisk eggs with grated cheeses and lots of black pepper.\n4. Drain pasta, reserving 1/2 cup pasta water.\n5. Quickly toss hot pasta with pancetta, then remove from heat and stir in egg mixture, adding pasta water as needed to create creamy sauce.\n6. Serve immediately with extra cheese and pepper.', 10, 15, 4, 'medium', 3, 1, 2, 'approved');
-- --------------------------------------------------------
-- Table structure for table `recipe_dietary`
-- --------------------------------------------------------
CREATE TABLE `recipe_dietary` (
`recipe_id` int(11) NOT NULL,
`dietary_id` int(11) NOT NULL,
PRIMARY KEY (`recipe_id`,`dietary_id`),
KEY `dietary_id` (`dietary_id`),
CONSTRAINT `recipe_dietary_ibfk_1` FOREIGN KEY (`recipe_id`) REFERENCES `recipes` (`id`) ON DELETE CASCADE,
CONSTRAINT `recipe_dietary_ibfk_2` FOREIGN KEY (`dietary_id`) REFERENCES `dietary_options` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
-- Table structure for table `ratings`
-- --------------------------------------------------------
CREATE TABLE `ratings` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`recipe_id` int(11) NOT NULL,
`user_id` int(11) DEFAULT NULL,
`rating` int(1) NOT NULL CHECK (rating >= 1 AND rating <= 5),
`review` text DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `unique_rating` (`recipe_id`,`user_id`),
KEY `user_id` (`user_id`),
CONSTRAINT `ratings_ibfk_1` FOREIGN KEY (`recipe_id`) REFERENCES `recipes` (`id`) ON DELETE CASCADE,
CONSTRAINT `ratings_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
-- Table structure for table `favorites`
-- --------------------------------------------------------
CREATE TABLE `favorites` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`recipe_id` int(11) NOT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `unique_favorite` (`user_id`,`recipe_id`),
KEY `recipe_id` (`recipe_id`),
CONSTRAINT `favorites_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
CONSTRAINT `favorites_ibfk_2` FOREIGN KEY (`recipe_id`) REFERENCES `recipes` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
-- Table structure for table `comments`
-- --------------------------------------------------------
CREATE TABLE `comments` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`recipe_id` int(11) NOT NULL,
`user_id` int(11) DEFAULT NULL,
`author_name` varchar(100) DEFAULT NULL,
`author_email` varchar(100) DEFAULT NULL,
`content` text NOT NULL,
`status` enum('pending','approved','spam','trash') DEFAULT 'pending',
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
KEY `recipe_id` (`recipe_id`),
KEY `user_id` (`user_id`),
CONSTRAINT `comments_ibfk_1` FOREIGN KEY (`recipe_id`) REFERENCES `recipes` (`id`) ON DELETE CASCADE,
CONSTRAINT `comments_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
-- Table structure for table `subscribers`
-- --------------------------------------------------------
CREATE TABLE `subscribers` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`email` varchar(100) NOT NULL,
`name` varchar(100) DEFAULT NULL,
`status` enum('active','inactive','unsubscribed') DEFAULT 'active',
`subscribed_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
-- Table structure for table `settings`
-- --------------------------------------------------------
CREATE TABLE `settings` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`setting_key` varchar(100) NOT NULL,
`setting_value` text DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `setting_key` (`setting_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `settings` (`setting_key`, `setting_value`) VALUES
('site_title', 'Recipe Sharing Community'),
('site_description', 'Share and discover amazing recipes from around the world'),
('site_logo', 'logo.png'),
('contact_email', '[email protected]'),
('recipes_per_page', '12'),
('enable_comments', '1'),
('comment_moderation', '1'),
('allow_submissions', '1');
COMMIT;

πŸ’» Core PHP Files

1. Database Configuration (includes/config.php)

<?php
// Database configuration
define('DB_HOST', 'localhost');
define('DB_NAME', 'recipe_db');
define('DB_USER', 'root');
define('DB_PASS', '');
// Site configuration
define('SITE_NAME', 'Recipe Sharing Community');
define('SITE_URL', 'http://localhost/recipe-sharing/');
define('UPLOAD_PATH', __DIR__ . '/../assets/images/recipes/');
define('AVATAR_PATH', __DIR__ . '/../assets/images/avatars/');
define('RECIPES_PER_PAGE', 12);
define('MAX_FILE_SIZE', 5 * 1024 * 1024); // 5MB
define('ALLOWED_EXTENSIONS', ['jpg', 'jpeg', 'png', 'gif', 'webp']);
try {
$pdo = new PDO(
"mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4",
DB_USER,
DB_PASS,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false
]
);
} catch (PDOException $e) {
die("Connection failed: " . $e->getMessage());
}
// Start session if not started
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
// Load settings
$settings = [];
$stmt = $pdo->query("SELECT setting_key, setting_value FROM settings");
while ($row = $stmt->fetch()) {
$settings[$row['setting_key']] = $row['setting_value'];
}
// Helper functions
function sanitize($data) {
$data = trim($data);
$data = stripslashes($data);
$data = htmlspecialchars($data);
return $data;
}
function redirect($url) {
header("Location: $url");
exit;
}
function isLoggedIn() {
return isset($_SESSION['user_id']);
}
function isAdmin() {
return isset($_SESSION['user_role']) && $_SESSION['user_role'] === 'admin';
}
function isModerator() {
return isset($_SESSION['user_role']) && in_array($_SESSION['user_role'], ['admin', 'moderator']);
}
function formatDate($date, $format = 'M d, Y') {
return date($format, strtotime($date));
}
function timeAgo($timestamp) {
$time_ago = strtotime($timestamp);
$current_time = time();
$time_difference = $current_time - $time_ago;
$seconds = $time_difference;
$minutes = round($seconds / 60);
$hours = round($seconds / 3600);
$days = round($seconds / 86400);
$weeks = round($seconds / 604800);
$months = round($seconds / 2629440);
$years = round($seconds / 31553280);
if ($seconds <= 60) {
return "Just now";
} else if ($minutes <= 60) {
return ($minutes == 1) ? "1 minute ago" : "$minutes minutes ago";
} else if ($hours <= 24) {
return ($hours == 1) ? "1 hour ago" : "$hours hours ago";
} else if ($days <= 7) {
return ($days == 1) ? "yesterday" : "$days days ago";
} else if ($weeks <= 4.3) {
return ($weeks == 1) ? "1 week ago" : "$weeks weeks ago";
} else if ($months <= 12) {
return ($months == 1) ? "1 month ago" : "$months months ago";
} else {
return ($years == 1) ? "1 year ago" : "$years years ago";
}
}
function createSlug($string) {
$string = strtolower($string);
$string = preg_replace('/[^a-z0-9-]/', '-', $string);
$string = preg_replace('/-+/', '-', $string);
return trim($string, '-');
}
function getRatingStars($rating) {
$stars = '';
for ($i = 1; $i <= 5; $i++) {
if ($i <= $rating) {
$stars .= 'β˜…';
} else if ($i - 0.5 <= $rating) {
$stars .= 'Β½';
} else {
$stars .= 'β˜†';
}
}
return $stars;
}
function formatTime($minutes) {
if ($minutes < 60) {
return $minutes . ' min';
}
$hours = floor($minutes / 60);
$mins = $minutes % 60;
return $hours . ' hr ' . ($mins > 0 ? $mins . ' min' : '');
}
?>

2. Authentication Functions (includes/auth.php)

<?php
require_once 'config.php';
class Auth {
public static function login($email, $password) {
global $pdo;
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = ? AND status = 'active'");
$stmt->execute([$email]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password'])) {
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['user_name'] = $user['full_name'];
$_SESSION['user_email'] = $user['email'];
$_SESSION['user_role'] = $user['role'];
$_SESSION['user_avatar'] = $user['avatar'];
// Update last login
$pdo->prepare("UPDATE users SET last_login = NOW() WHERE id = ?")->execute([$user['id']]);
return true;
}
return false;
}
public static function register($data) {
global $pdo;
// Check if email exists
$stmt = $pdo->prepare("SELECT id FROM users WHERE email = ?");
$stmt->execute([$data['email']]);
if ($stmt->fetch()) {
return ['success' => false, 'message' => 'Email already registered'];
}
// Check if username exists
$stmt = $pdo->prepare("SELECT id FROM users WHERE username = ?");
$stmt->execute([$data['username']]);
if ($stmt->fetch()) {
return ['success' => false, 'message' => 'Username already taken'];
}
// Hash password
$hashedPassword = password_hash($data['password'], PASSWORD_DEFAULT);
// Handle avatar upload
$avatar = 'default-avatar.png';
if (isset($data['avatar']) && !empty($data['avatar'])) {
$avatar = $data['avatar'];
}
// Insert user
$stmt = $pdo->prepare("
INSERT INTO users (username, email, password, full_name, bio, avatar) 
VALUES (?, ?, ?, ?, ?, ?)
");
$success = $stmt->execute([
$data['username'],
$data['email'],
$hashedPassword,
$data['full_name'],
$data['bio'] ?? null,
$avatar
]);
if ($success) {
return ['success' => true, 'message' => 'Registration successful! Please login.'];
}
return ['success' => false, 'message' => 'Registration failed. Please try again.'];
}
public static function logout() {
session_destroy();
redirect('index.php');
}
public static function checkLogin() {
if (!isset($_SESSION['user_id'])) {
redirect('login.php');
}
}
public static function checkAdmin() {
self::checkLogin();
if ($_SESSION['user_role'] !== 'admin') {
redirect('index.php');
}
}
public static function checkModerator() {
self::checkLogin();
if (!in_array($_SESSION['user_role'], ['admin', 'moderator'])) {
redirect('index.php');
}
}
public static function getCurrentUser() {
global $pdo;
if (!isset($_SESSION['user_id'])) {
return null;
}
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$_SESSION['user_id']]);
return $stmt->fetch();
}
}
?>

3. Homepage (index.php)

<?php
require_once 'includes/config.php';
// Get featured recipes
$featured = $pdo->query("
SELECT r.*, u.username as author_name, u.avatar as author_avatar,
c.name as category_name, c.slug as category_slug
FROM recipes r
JOIN users u ON r.author_id = u.id
JOIN categories c ON r.category_id = c.id
WHERE r.status = 'featured'
ORDER BY r.created_at DESC
LIMIT 6
")->fetchAll();
// Get latest recipes
$latest = $pdo->prepare("
SELECT r.*, u.username as author_name,
c.name as category_name, c.slug as category_slug
FROM recipes r
JOIN users u ON r.author_id = u.id
JOIN categories c ON r.category_id = c.id
WHERE r.status IN ('approved', 'featured')
ORDER BY r.created_at DESC
LIMIT ?
");
$latest->execute([RECIPES_PER_PAGE]);
$latest_recipes = $latest->fetchAll();
// Get popular recipes (by views)
$popular = $pdo->query("
SELECT r.*, u.username as author_name,
c.name as category_name
FROM recipes r
JOIN users u ON r.author_id = u.id
JOIN categories c ON r.category_id = c.id
WHERE r.status IN ('approved', 'featured')
ORDER BY r.views DESC
LIMIT 5
")->fetchAll();
// Get categories for navigation
$categories = $pdo->query("
SELECT * FROM categories 
WHERE status = 'active' 
ORDER BY menu_order
")->fetchAll();
// Get cuisines
$cuisines = $pdo->query("SELECT * FROM cuisines ORDER BY name")->fetchAll();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo SITE_NAME; ?> - Share and Discover Amazing Recipes</title>
<meta name="description" content="Discover, share, and save delicious recipes from around the world">
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
<!-- Header -->
<header class="site-header">
<div class="top-header">
<div class="container">
<div class="header-left">
<span class="welcome-message">Welcome to <?php echo SITE_NAME; ?></span>
</div>
<div class="header-right">
<?php if (isLoggedIn()): ?>
<span>Hello, <?php echo $_SESSION['user_name']; ?></span>
<a href="dashboard.php">My Dashboard</a>
<a href="submit-recipe.php">βž• Share Recipe</a>
<?php if (isModerator()): ?>
<a href="admin/dashboard.php">Admin</a>
<?php endif; ?>
<a href="logout.php">Logout</a>
<?php else: ?>
<a href="login.php">Login</a>
<a href="register.php">Register</a>
<a href="submit-recipe.php">βž• Share Recipe</a>
<?php endif; ?>
</div>
</div>
</div>
<div class="main-header">
<div class="container">
<div class="logo">
<a href="index.php">
<h1>🍳 <?php echo SITE_NAME; ?></h1>
</a>
</div>
<div class="search-bar">
<form action="search.php" method="GET">
<input type="text" name="q" placeholder="Search recipes, ingredients, cuisines..." 
id="search-input" autocomplete="off">
<button type="submit">πŸ”</button>
</form>
<div id="search-suggestions" class="search-suggestions"></div>
</div>
</div>
</div>
<nav class="main-nav">
<div class="container">
<ul class="nav-menu">
<li><a href="index.php" class="active">Home</a></li>
<li><a href="recipes.php">All Recipes</a></li>
<?php foreach (array_slice($categories, 0, 5) as $category): ?>
<li>
<a href="category.php?slug=<?php echo $category['slug']; ?>">
<?php echo $category['icon'] . ' ' . $category['name']; ?>
</a>
</li>
<?php endforeach; ?>
<li class="dropdown">
<a href="#">More β–Ύ</a>
<ul class="dropdown-menu">
<?php foreach (array_slice($categories, 5) as $category): ?>
<li>
<a href="category.php?slug=<?php echo $category['slug']; ?>">
<?php echo $category['icon'] . ' ' . $category['name']; ?>
</a>
</li>
<?php endforeach; ?>
<li class="divider"></li>
<?php foreach ($cuisines as $cuisine): ?>
<li>
<a href="recipes.php?cuisine=<?php echo $cuisine['slug']; ?>">
<?php echo $cuisine['name']; ?>
</a>
</li>
<?php endforeach; ?>
</ul>
</li>
</ul>
</div>
</nav>
</header>
<!-- Hero Section -->
<section class="hero">
<div class="container">
<div class="hero-content">
<h2>Discover & Share Amazing Recipes</h2>
<p>Join our community of food lovers and share your culinary creations</p>
<div class="hero-buttons">
<a href="recipes.php" class="btn btn-large">Browse Recipes</a>
<a href="submit-recipe.php" class="btn btn-large btn-secondary">Share Your Recipe</a>
</div>
</div>
</div>
</section>
<!-- Featured Recipes -->
<?php if (!empty($featured)): ?>
<section class="featured-section">
<div class="container">
<h2 class="section-title">🌟 Featured Recipes</h2>
<div class="recipe-grid featured-grid">
<?php foreach ($featured as $recipe): ?>
<div class="recipe-card">
<a href="recipe.php?slug=<?php echo $recipe['slug']; ?>">
<div class="recipe-image">
<img src="assets/images/recipes/<?php echo $recipe['featured_image'] ?: 'default-recipe.jpg'; ?>" 
alt="<?php echo $recipe['title']; ?>">
<span class="category-badge"><?php echo $recipe['category_name']; ?></span>
</div>
<div class="recipe-content">
<h3><?php echo $recipe['title']; ?></h3>
<p><?php echo substr($recipe['description'], 0, 100); ?>...</p>
<div class="recipe-meta">
<span class="author">
<img src="assets/images/avatars/<?php echo $recipe['author_avatar']; ?>" 
alt="<?php echo $recipe['author_name']; ?>" class="mini-avatar">
<?php echo $recipe['author_name']; ?>
</span>
<span class="rating"><?php echo getRatingStars($recipe['avg_rating']); ?></span>
</div>
</div>
</a>
</div>
<?php endforeach; ?>
</div>
</div>
</section>
<?php endif; ?>
<!-- Main Content -->
<div class="container">
<div class="content-wrapper">
<!-- Main Content Area -->
<div class="main-content">
<section class="latest-recipes">
<h2 class="section-title">πŸ“– Latest Recipes</h2>
<div class="recipe-grid" id="recipe-grid">
<?php foreach ($latest_recipes as $recipe): ?>
<div class="recipe-card">
<a href="recipe.php?slug=<?php echo $recipe['slug']; ?>">
<div class="recipe-image">
<img src="assets/images/recipes/<?php echo $recipe['featured_image'] ?: 'default-recipe.jpg'; ?>" 
alt="<?php echo $recipe['title']; ?>">
<span class="category-badge"><?php echo $recipe['category_name']; ?></span>
</div>
<div class="recipe-content">
<h3><?php echo $recipe['title']; ?></h3>
<p><?php echo substr($recipe['description'], 0, 100); ?>...</p>
<div class="recipe-meta">
<span class="author">By <?php echo $recipe['author_name']; ?></span>
<span class="rating"><?php echo getRatingStars($recipe['avg_rating']); ?></span>
</div>
</div>
</a>
</div>
<?php endforeach; ?>
</div>
<!-- Load More Button -->
<div class="load-more">
<button id="load-more" class="btn" data-page="2">Load More Recipes</button>
</div>
</section>
</div>
<!-- Sidebar -->
<aside class="sidebar">
<!-- Popular Recipes -->
<div class="widget popular-widget">
<h3 class="widget-title">πŸ”₯ Popular Recipes</h3>
<ul class="popular-list">
<?php foreach ($popular as $recipe): ?>
<li>
<a href="recipe.php?slug=<?php echo $recipe['slug']; ?>">
<span class="popular-title"><?php echo $recipe['title']; ?></span>
<span class="popular-meta">
<span class="views">πŸ‘οΈ <?php echo number_format($recipe['views']); ?> views</span>
<span class="rating"><?php echo getRatingStars($recipe['avg_rating']); ?></span>
</span>
</a>
</li>
<?php endforeach; ?>
</ul>
</div>
<!-- Categories Widget -->
<div class="widget categories-widget">
<h3 class="widget-title">πŸ“š Categories</h3>
<ul class="categories-list">
<?php foreach ($categories as $category): ?>
<li>
<a href="category.php?slug=<?php echo $category['slug']; ?>">
<span class="category-icon"><?php echo $category['icon']; ?></span>
<span class="category-name"><?php echo $category['name']; ?></span>
</a>
</li>
<?php endforeach; ?>
</ul>
</div>
<!-- Dietary Options -->
<div class="widget dietary-widget">
<h3 class="widget-title">πŸ₯— Dietary Preferences</h3>
<div class="dietary-tags">
<?php
$dietary = $pdo->query("SELECT * FROM dietary_options ORDER BY name")->fetchAll();
foreach ($dietary as $diet): ?>
<a href="recipes.php?diet=<?php echo $diet['slug']; ?>" class="diet-tag">
<?php echo $diet['icon'] . ' ' . $diet['name']; ?>
</a>
<?php endforeach; ?>
</div>
</div>
<!-- Newsletter -->
<div class="widget newsletter-widget">
<h3 class="widget-title">πŸ“§ Newsletter</h3>
<p>Get the latest recipes delivered to your inbox</p>
<form id="newsletter-form" class="newsletter-form">
<input type="email" name="email" placeholder="Your email address" required>
<input type="text" name="name" placeholder="Your name (optional)">
<button type="submit" class="btn btn-block">Subscribe</button>
</form>
<div id="newsletter-message"></div>
</div>
</aside>
</div>
</div>
<!-- Footer -->
<footer class="site-footer">
<div class="container">
<div class="footer-widgets">
<div class="footer-widget">
<h4>About Us</h4>
<p><?php echo SITE_NAME; ?> is a community-driven platform where food lovers share and discover amazing recipes from around the world.</p>
</div>
<div class="footer-widget">
<h4>Quick Links</h4>
<ul>
<li><a href="about.php">About Us</a></li>
<li><a href="contact.php">Contact</a></li>
<li><a href="privacy.php">Privacy Policy</a></li>
<li><a href="terms.php">Terms of Use</a></li>
<li><a href="submit-recipe.php">Submit Recipe</a></li>
</ul>
</div>
<div class="footer-widget">
<h4>Follow Us</h4>
<div class="social-links">
<a href="#" target="_blank">πŸ“˜ Facebook</a>
<a href="#" target="_blank">🐦 Twitter</a>
<a href="#" target="_blank">πŸ“Έ Instagram</a>
<a href="#" target="_blank">πŸ“Œ Pinterest</a>
</div>
</div>
</div>
<div class="footer-bottom">
<p>&copy; <?php echo date('Y'); ?> <?php echo SITE_NAME; ?>. All rights reserved.</p>
</div>
</div>
</footer>
<script src="assets/js/main.js"></script>
<script src="assets/js/masonry.js"></script>
<script>
// Live search suggestions
document.getElementById('search-input')?.addEventListener('input', function(e) {
const query = e.target.value;
if (query.length < 2) {
document.getElementById('search-suggestions').style.display = 'none';
return;
}
fetch(`api/search-suggestions.php?q=${encodeURIComponent(query)}`)
.then(response => response.json())
.then(data => {
const suggestions = document.getElementById('search-suggestions');
if (data.length > 0) {
suggestions.innerHTML = data.map(item => 
`<a href="recipe.php?slug=${item.slug}">
<img src="assets/images/recipes/${item.image || 'default-recipe.jpg'}" alt="">
<span>${item.title}</span>
</a>`
).join('');
suggestions.style.display = 'block';
} else {
suggestions.style.display = 'none';
}
});
});
// Newsletter subscription
document.getElementById('newsletter-form')?.addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
fetch('api/subscribe.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
const message = document.getElementById('newsletter-message');
message.className = data.success ? 'success' : 'error';
message.textContent = data.message;
if (data.success) {
this.reset();
}
});
});
</script>
</body>
</html>

4. Recipe Page (recipe.php)

```php
<?php
require_once 'includes/config.php';

$slug = $_GET['slug'] ?? '';

if (empty($slug)) {
redirect('recipes.php');
}

// Get recipe details
$stmt = $pdo->prepare("
SELECT r.*, u.username as author_name, u.full_name as author_full_name,
u.bio as author_bio, u.avatar as author_avatar,
c.name as category_name, c.slug as category_slug,
cu.name as cuisine_name
FROM recipes r
JOIN users u ON r.author_id = u.id
JOIN categories c ON r.category_id = c.id
LEFT JOIN cuisines cu ON r.cuisine_id = cu.id
WHERE r.slug = ? AND r.status IN ('approved', 'featured')
");
$stmt->execute([$slug]);
$recipe = $stmt->fetch();

if (!$recipe) {
redirect('recipes.php');
}

// Increment view count
$pdo->prepare("UPDATE recipes SET views = views + 1 WHERE id = ?")->execute([$recipe['id']]);

// Get dietary options for this recipe
$dietary = $pdo->prepare("
SELECT d.* FROM dietary_options d
JOIN recipe_dietary rd ON d.id = rd.dietary_id
WHERE rd.recipe_id = ?
");
$dietary->execute([$recipe['id']]);
$dietary_options = $dietary->fetchAll();

// Get ratings/reviews
$ratings = $pdo->prepare("
SELECT r.*, u.username, u.avatar
FROM ratings r
LEFT JOIN users u ON r.user_id = u.id
WHERE r.recipe_id = ?
ORDER BY r.created_at DESC
");
$ratings->execute([$recipe['id']]);
$reviews = $ratings->fetchAll();

// Get comments
$comments = $pdo->prepare("
SELECT c.*, u.username, u.avatar
FROM comments c
LEFT JOIN users u ON c.user_id = u.id
WHERE c.recipe_id = ? AND c.status = 'approved'
ORDER BY c.created_at DESC
");
$comments->execute([$recipe['id']]);
$recipe_comments = $comments->fetchAll();

// Check if user has saved this recipe
$is_saved = false;
if (isLoggedIn()) {
$check = $pdo->prepare("SELECT id FROM favorites WHERE user_id = ? AND recipe_id = ?");
$check->execute([$_SESSION['user_id'], $recipe['id']]);
$is_saved = $check->fetch() ? true : false;
}

// Check if user has rated this recipe
$user_rating = null;
if (isLoggedIn()) {
$check = $pdo->prepare("SELECT rating FROM ratings WHERE user_id = ? AND recipe_id = ?");
$check->execute([$_SESSION['user_id'], $recipe['id']]);
$user_rating = $check->fetchColumn();
}

// Handle rating submission
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['submit_rating']) && isLoggedIn()) {
$rating = (int)$_POST['rating'];
$review = sanitize($_POST['review'] ?? '');

if ($rating >= 1 && $rating <= 5) {
// Check if user already rated
$check = $pdo->prepare("SELECT id FROM ratings WHERE user_id = ? AND recipe_id = ?");
$check->execute([$_SESSION['user_id'], $recipe['id']]);
if ($check->fetch()) {
// Update existing rating
$stmt = $pdo->prepare("
UPDATE ratings SET rating = ?, review = ? 
WHERE user_id = ? AND recipe_id = ?
");
$stmt->execute([$rating, $review, $_SESSION['user_id'], $recipe['id']]);
} else {
// Insert new rating
$stmt = $pdo->prepare("
INSERT INTO ratings (recipe_id, user_id, rating, review)
VALUES (?, ?, ?, ?)
");
$stmt->execute([$recipe['id'], $_SESSION['user_id'], $rating, $review]);
}
// Update recipe average rating
$avg = $pdo->prepare("
SELECT AVG(rating) as avg, COUNT(*) as count 
FROM ratings WHERE recipe_id = ?
");
$avg->execute([$recipe['id']]);
$rating_data = $avg->fetch();
$pdo->prepare("
UPDATE recipes SET avg_rating = ?, rating_count = ? WHERE id = ?
")->execute([$rating_data['avg'], $rating_data['count'], $recipe['id']]);
redirect('recipe.php?slug=' . $slug . '#ratings');
}

}

// Handle comment submission
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['submit_comment'])) {
$content = sanitize($_POST['content']);

if (!empty($content)) {
if (isLoggedIn()) {
$stmt = $pdo->prepare("
INSERT INTO comments (recipe_id, user_id, content, status)
VALUES (?, ?, ?, ?)
");
$status = $settings['comment_moderation'] == '1' ? 'pending' : 'approved';
$stmt->execute([$recipe['id'], $_SESSION['user_id'], $content, $status]);
} else {
$name = sanitize($_POST['name']);
$email = sanitize($_POST['email']);
if (!empty($name) && !empty($email) && filter_var($email, FILTER_VALIDATE_EMAIL)) {
$stmt = $pdo->prepare("
INSERT INTO comments (recipe_id, author_name, author_email, content, status)
VALUES (?, ?, ?, ?, ?)
");
$status = $settings['comment_moderation'] == '1' ? 'pending' : 'approved';
$stmt->execute([$recipe['id'], $name, $email, $content, $status]);
}
}
redirect('recipe.php?slug=' . $slug . '#comments');
}

}

// Parse gallery images
$gallery = $recipe['gallery_images'] ? json_decode($recipe['gallery_images'], true) : [];
?>


<?php echo $recipe['title']; ?> - <?php echo SITE_NAME; ?>


<!-- Recipe Page -->
<main class="container">
<div class="recipe-page">
<div class="recipe-header">
<h1 class="recipe-title"><?php echo $recipe['title']; ?></h1>
<div class="recipe-meta-top">
<div class="meta-item">
<span class="meta-label">By</span>
<span class="meta-value">
<img src="assets/images/avatars/<?php echo $recipe['author_avatar']; ?>" 
alt="<?php echo $recipe['author_name']; ?>" class
                                 class="mini-avatar">
<?php echo $recipe['author_full_name']; ?>
</span>
</div>
<div class="meta-item">
<span class="meta-label">Category</span>
<span class="meta-value">
<a href="category.php?slug=<?php echo $recipe['category_slug']; ?>">
<?php echo $recipe['category_name']; ?>
</a>
</span>
</div>
<?php if ($recipe['cuisine_name']): ?>
<div class="meta-item">
<span class="meta-label">Cuisine</span>
<span class="meta-value"><?php echo $recipe['cuisine_name']; ?></span>
</div>
<?php endif; ?>
<div class="meta-item">
<span class="meta-label">Published</span>
<span class="meta-value"><?php echo formatDate($recipe['created_at']); ?></span>
</div>
</div>
<div class="recipe-actions">
<?php if (isLoggedIn()): ?>
<button class="btn <?php echo $is_saved ? 'btn-success' : 'btn-primary'; ?>" 
id="save-recipe" 
data-recipe-id="<?php echo $recipe['id']; ?>">
<?php echo $is_saved ? 'βœ“ Saved' : '❀️ Save Recipe'; ?>
</button>
<?php endif; ?>
<button class="btn" onclick="window.print()">πŸ–¨οΈ Print</button>
<div class="share-buttons">
<a href="https://www.facebook.com/sharer/sharer.php?u=<?php echo urlencode(SITE_URL . 'recipe.php?slug=' . $recipe['slug']); ?>" 
target="_blank" class="share-btn facebook">f</a>
<a href="https://twitter.com/intent/tweet?url=<?php echo urlencode(SITE_URL . 'recipe.php?slug=' . $recipe['slug']); ?>&text=<?php echo urlencode($recipe['title']); ?>" 
target="_blank" class="share-btn twitter">t</a>
<a href="https://pinterest.com/pin/create/button/?url=<?php echo urlencode(SITE_URL . 'recipe.php?slug=' . $recipe['slug']); ?>&media=<?php echo urlencode(SITE_URL . 'assets/images/recipes/' . ($recipe['featured_image'] ?: 'default-recipe.jpg')); ?>&description=<?php echo urlencode($recipe['title']); ?>" 
target="_blank" class="share-btn pinterest">p</a>
</div>
</div>
</div>
<div class="recipe-content-wrapper">
<!-- Main Content -->
<div class="recipe-main">
<!-- Featured Image -->
<?php if ($recipe['featured_image']): ?>
<div class="recipe-featured-image">
<img src="assets/images/recipes/<?php echo $recipe['featured_image']; ?>" 
alt="<?php echo $recipe['title']; ?>">
</div>
<?php endif; ?>
<!-- Description -->
<div class="recipe-description">
<h2>Description</h2>
<p><?php echo nl2br($recipe['description']); ?></p>
</div>
<!-- Recipe Info Box -->
<div class="recipe-info-box">
<div class="info-item">
<span class="info-icon">⏱️</span>
<span class="info-label">Prep Time</span>
<span class="info-value"><?php echo $recipe['prep_time'] ? formatTime($recipe['prep_time']) : 'N/A'; ?></span>
</div>
<div class="info-item">
<span class="info-icon">🍳</span>
<span class="info-label">Cook Time</span>
<span class="info-value"><?php echo $recipe['cook_time'] ? formatTime($recipe['cook_time']) : 'N/A'; ?></span>
</div>
<div class="info-item">
<span class="info-icon">⏲️</span>
<span class="info-label">Total Time</span>
<span class="info-value"><?php echo $recipe['total_time'] ? formatTime($recipe['total_time']) : 'N/A'; ?></span>
</div>
<div class="info-item">
<span class="info-icon">🍽️</span>
<span class="info-label">Servings</span>
<span class="info-value"><?php echo $recipe['servings'] ?: 'N/A'; ?></span>
</div>
<div class="info-item">
<span class="info-icon">πŸ“Š</span>
<span class="info-label">Difficulty</span>
<span class="info-value"><?php echo ucfirst($recipe['difficulty']); ?></span>
</div>
<?php if ($recipe['calories']): ?>
<div class="info-item">
<span class="info-icon">πŸ”₯</span>
<span class="info-label">Calories</span>
<span class="info-value"><?php echo $recipe['calories']; ?> kcal</span>
</div>
<?php endif; ?>
</div>
<!-- Dietary Tags -->
<?php if (!empty($dietary_options)): ?>
<div class="dietary-tags-section">
<h2>Dietary Options</h2>
<div class="dietary-tags">
<?php foreach ($dietary_options as $diet): ?>
<span class="diet-tag">
<?php echo $diet['icon'] . ' ' . $diet['name']; ?>
</span>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<!-- Ingredients -->
<div class="ingredients-section">
<h2>Ingredients</h2>
<div class="ingredients-list">
<?php
$ingredients = explode("\n", $recipe['ingredients']);
foreach ($ingredients as $ingredient):
if (trim($ingredient)):
?>
<div class="ingredient-item">
<input type="checkbox" id="ingredient-<?php echo md5($ingredient); ?>">
<label for="ingredient-<?php echo md5($ingredient); ?>">
<?php echo trim($ingredient); ?>
</label>
</div>
<?php 
endif;
endforeach; 
?>
</div>
</div>
<!-- Instructions -->
<div class="instructions-section">
<h2>Instructions</h2>
<div class="instructions-list">
<?php
$instructions = explode("\n", $recipe['instructions']);
$step = 1;
foreach ($instructions as $instruction):
if (trim($instruction)):
?>
<div class="instruction-step">
<span class="step-number"><?php echo $step++; ?></span>
<p><?php echo trim($instruction); ?></p>
</div>
<?php 
endif;
endforeach; 
?>
</div>
</div>
<!-- Video (if available) -->
<?php if ($recipe['video_url']): ?>
<div class="video-section">
<h2>Video Tutorial</h2>
<div class="video-container">
<iframe src="<?php echo $recipe['video_url']; ?>" 
frameborder="0" 
allowfullscreen></iframe>
</div>
</div>
<?php endif; ?>
<!-- Gallery -->
<?php if (!empty($gallery)): ?>
<div class="gallery-section">
<h2>Gallery</h2>
<div class="gallery-grid">
<?php foreach ($gallery as $image): ?>
<a href="assets/images/recipes/<?php echo $image; ?>" 
class="gallery-item" 
data-lightbox="recipe-gallery">
<img src="assets/images/recipes/<?php echo $image; ?>" alt="">
</a>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</div>
<!-- Sidebar -->
<div class="recipe-sidebar">
<!-- Author Box -->
<div class="author-box">
<img src="assets/images/avatars/<?php echo $recipe['author_avatar']; ?>" 
alt="<?php echo $recipe['author_name']; ?>" 
class="author-avatar-large">
<h3><?php echo $recipe['author_full_name']; ?></h3>
<p class="author-username">@<?php echo $recipe['author_name']; ?></p>
<?php if ($recipe['author_bio']): ?>
<p class="author-bio"><?php echo $recipe['author_bio']; ?></p>
<?php endif; ?>
<a href="user-profile.php?user=<?php echo $recipe['author_name']; ?>" 
class="btn btn-block">View Profile</a>
</div>
<!-- Recipe Stats -->
<div class="stats-box">
<div class="stat-item">
<span class="stat-icon">πŸ‘οΈ</span>
<span class="stat-value"><?php echo number_format($recipe['views']); ?></span>
<span class="stat-label">Views</span>
</div>
<div class="stat-item">
<span class="stat-icon">⭐</span>
<span class="stat-value"><?php echo number_format($recipe['avg_rating'], 1); ?></span>
<span class="stat-label">Rating (<?php echo $recipe['rating_count']; ?>)</span>
</div>
<div class="stat-item">
<span class="stat-icon">πŸ’¬</span>
<span class="stat-value"><?php echo count($recipe_comments); ?></span>
<span class="stat-label">Comments</span>
</div>
</div>
<!-- Nutrition Info (if available) -->
<?php if ($recipe['calories']): ?>
<div class="nutrition-box">
<h3>Nutrition Info (per serving)</h3>
<div class="nutrition-item">
<span>Calories</span>
<span><?php echo $recipe['calories']; ?> kcal</span>
</div>
<!-- Add more nutrition info if available -->
</div>
<?php endif; ?>
</div>
</div>
<!-- Ratings & Reviews Section -->
<section id="ratings" class="ratings-section">
<h2>Ratings & Reviews</h2>
<!-- Rating Summary -->
<div class="rating-summary">
<div class="average-rating">
<span class="big-rating"><?php echo number_format($recipe['avg_rating'], 1); ?></span>
<span class="rating-stars"><?php echo getRatingStars($recipe['avg_rating']); ?></span>
<span class="rating-count">(<?php echo $recipe['rating_count']; ?> reviews)</span>
</div>
<?php if (isLoggedIn()): ?>
<div class="your-rating">
<h3>Your Rating</h3>
<form method="POST" action="" class="rating-form">
<div class="star-rating">
<?php for ($i = 5; $i >= 1; $i--): ?>
<input type="radio" id="star<?php echo $i; ?>" name="rating" value="<?php echo $i; ?>" 
<?php echo $user_rating == $i ? 'checked' : ''; ?>>
<label for="star<?php echo $i; ?>">β˜…</label>
<?php endfor; ?>
</div>
<textarea name="review" placeholder="Write your review (optional)"><?php echo $user_review ?? ''; ?></textarea>
<button type="submit" name="submit_rating" class="btn">Submit Rating</button>
</form>
</div>
<?php else: ?>
<p class="login-prompt">
<a href="login.php">Login</a> to rate and review this recipe.
</p>
<?php endif; ?>
</div>
<!-- Reviews List -->
<?php if (!empty($reviews)): ?>
<div class="reviews-list">
<?php foreach ($reviews as $review): ?>
<div class="review-item">
<div class="review-header">
<img src="assets/images/avatars/<?php echo $review['avatar'] ?: 'default-avatar.png'; ?>" 
alt="<?php echo $review['username']; ?>" 
class="review-avatar">
<div class="review-meta">
<span class="review-author"><?php echo $review['username']; ?></span>
<span class="review-date"><?php echo timeAgo($review['created_at']); ?></span>
</div>
<div class="review-rating">
<?php echo getRatingStars($review['rating']); ?>
</div>
</div>
<?php if ($review['review']): ?>
<p class="review-content"><?php echo nl2br($review['review']); ?></p>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</section>
<!-- Comments Section -->
<section id="comments" class="comments-section">
<h2>Comments (<?php echo count($recipe_comments); ?>)</h2>
<!-- Comment Form -->
<div class="comment-form-wrapper">
<h3>Leave a Comment</h3>
<form method="POST" action="" class="comment-form">
<div class="form-group">
<label for="comment-content">Your Comment *</label>
<textarea id="comment-content" name="content" rows="4" required></textarea>
</div>
<?php if (!isLoggedIn()): ?>
<div class="form-row">
<div class="form-group col-md-6">
<label for="comment-name">Name *</label>
<input type="text" id="comment-name" name="name" required>
</div>
<div class="form-group col-md-6">
<label for="comment-email">Email *</label>
<input type="email" id="comment-email" name="email" required>
</div>
</div>
<?php endif; ?>
<button type="submit" name="submit_comment" class="btn">Post Comment</button>
</form>
</div>
<!-- Comments List -->
<?php if (!empty($recipe_comments)): ?>
<div class="comments-list">
<?php foreach ($recipe_comments as $comment): ?>
<div class="comment">
<div class="comment-avatar">
<img src="assets/images/avatars/<?php echo $comment['avatar'] ?: 'default-avatar.png'; ?>" 
alt="<?php echo $comment['username'] ?: $comment['author_name']; ?>">
</div>
<div class="comment-content">
<div class="comment-meta">
<span class="comment-author">
<?php echo $comment['username'] ?: $comment['author_name']; ?>
</span>
<span class="comment-date">
<?php echo timeAgo($comment['created_at']); ?>
</span>
</div>
<div class="comment-text">
<?php echo nl2br($comment['content']); ?>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</section>
</div>
</main>
<!-- Footer -->
<?php include 'includes/footer.php'; ?>
<script src="assets/js/main.js"></script>
<script>
// Save recipe functionality
document.getElementById('save-recipe')?.addEventListener('click', function() {
const recipeId = this.dataset.recipeId;
fetch('api/save-recipe.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'recipe_id=' + recipeId
})
.then(response => response.json())
.then(data => {
if (data.success) {
this.textContent = data.saved ? 'βœ“ Saved' : '❀️ Save Recipe';
this.classList.toggle('btn-success', data.saved);
this.classList.toggle('btn-primary', !data.saved);
}
});
});
// Lightbox for gallery
document.querySelectorAll('[data-lightbox]').forEach(item => {
item.addEventListener('click', function(e) {
e.preventDefault();
// Simple lightbox implementation
const imgSrc = this.href;
const modal = document.createElement('div');
modal.className = 'lightbox-modal';
modal.innerHTML = `
<span class="close">&times;</span>
<img src="${imgSrc}" class="lightbox-image">
`;
document.body.appendChild(modal);
modal.querySelector('.close').addEventListener('click', () => {
modal.remove();
});
modal.addEventListener('click', function(e) {
if (e.target === this) {
this.remove();
}
});
});
});
</script>
</body>
</html>

5. Submit Recipe Page (submit-recipe.php)

<?php
require_once 'includes/config.php';
require_once 'includes/auth.php';
// Check if submissions are allowed
if ($settings['allow_submissions'] != '1') {
$_SESSION['error'] = 'Recipe submissions are currently closed';
redirect('index.php');
}
// Get categories, cuisines, dietary options for form
$categories = $pdo->query("SELECT * FROM categories WHERE status = 'active' ORDER BY name")->fetchAll();
$cuisines = $pdo->query("SELECT * FROM cuisines ORDER BY name")->fetchAll();
$dietary_options = $pdo->query("SELECT * FROM dietary_options ORDER BY name")->fetchAll();
$errors = [];
$form_data = [
'title' => '',
'description' => '',
'ingredients' => '',
'instructions' => '',
'prep_time' => '',
'cook_time' => '',
'total_time' => '',
'servings' => '',
'difficulty' => 'medium',
'calories' => '',
'category_id' => '',
'cuisine_id' => '',
'dietary' => [],
'video_url' => ''
];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Sanitize input
$form_data['title'] = sanitize($_POST['title']);
$form_data['description'] = sanitize($_POST['description']);
$form_data['ingredients'] = $_POST['ingredients'];
$form_data['instructions'] = $_POST['instructions'];
$form_data['prep_time'] = !empty($_POST['prep_time']) ? (int)$_POST['prep_time'] : null;
$form_data['cook_time'] = !empty($_POST['cook_time']) ? (int)$_POST['cook_time'] : null;
$form_data['total_time'] = !empty($_POST['total_time']) ? (int)$_POST['total_time'] : null;
$form_data['servings'] = !empty($_POST['servings']) ? (int)$_POST['servings'] : null;
$form_data['difficulty'] = $_POST['difficulty'];
$form_data['calories'] = !empty($_POST['calories']) ? (int)$_POST['calories'] : null;
$form_data['category_id'] = (int)$_POST['category_id'];
$form_data['cuisine_id'] = !empty($_POST['cuisine_id']) ? (int)$_POST['cuisine_id'] : null;
$form_data['dietary'] = $_POST['dietary'] ?? [];
$form_data['video_url'] = sanitize($_POST['video_url'] ?? '');
// Validate
if (empty($form_data['title'])) {
$errors['title'] = 'Title is required';
}
if (empty($form_data['description'])) {
$errors['description'] = 'Description is required';
}
if (empty($form_data['ingredients'])) {
$errors['ingredients'] = 'Ingredients are required';
}
if (empty($form_data['instructions'])) {
$errors['instructions'] = 'Instructions are required';
}
if (empty($form_data['category_id'])) {
$errors['category_id'] = 'Category is required';
}
// Handle featured image upload
$featured_image = null;
if (isset($_FILES['featured_image']) && $_FILES['featured_image']['error'] == 0) {
$allowed = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (!in_array($_FILES['featured_image']['type'], $allowed)) {
$errors['featured_image'] = 'Only JPG, PNG, GIF, and WEBP images are allowed';
} elseif ($_FILES['featured_image']['size'] > MAX_FILE_SIZE) {
$errors['featured_image'] = 'Image size must be less than 5MB';
} else {
$extension = pathinfo($_FILES['featured_image']['name'], PATHINFO_EXTENSION);
$filename = uniqid() . '.' . $extension;
$upload_path = UPLOAD_PATH . $filename;
if (move_uploaded_file($_FILES['featured_image']['tmp_name'], $upload_path)) {
$featured_image = $filename;
} else {
$errors['featured_image'] = 'Failed to upload image';
}
}
}
// Handle gallery images
$gallery_images = [];
if (isset($_FILES['gallery_images']) && !empty($_FILES['gallery_images']['name'][0])) {
$total = count($_FILES['gallery_images']['name']);
for ($i = 0; $i < $total; $i++) {
if ($_FILES['gallery_images']['error'][$i] == 0) {
$allowed = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (in_array($_FILES['gallery_images']['type'][$i], $allowed)) {
if ($_FILES['gallery_images']['size'][$i] <= MAX_FILE_SIZE) {
$extension = pathinfo($_FILES['gallery_images']['name'][$i], PATHINFO_EXTENSION);
$filename = uniqid() . '-' . $i . '.' . $extension;
$upload_path = UPLOAD_PATH . $filename;
if (move_uploaded_file($_FILES['gallery_images']['tmp_name'][$i], $upload_path)) {
$gallery_images[] = $filename;
}
}
}
}
}
}
if (empty($errors)) {
try {
$pdo->beginTransaction();
// Create slug
$slug = createSlug($form_data['title']);
// Check if slug exists and make it unique
$check = $pdo->prepare("SELECT id FROM recipes WHERE slug = ?");
$check->execute([$slug]);
if ($check->fetch()) {
$slug .= '-' . uniqid();
}
// Calculate total time if not provided
if (empty($form_data['total_time']) && ($form_data['prep_time'] || $form_data['cook_time'])) {
$form_data['total_time'] = ($form_data['prep_time'] ?? 0) + ($form_data['cook_time'] ?? 0);
}
// Insert recipe
$stmt = $pdo->prepare("
INSERT INTO recipes (
title, slug, description, ingredients, instructions, 
prep_time, cook_time, total_time, servings, difficulty, calories,
featured_image, gallery_images, video_url, category_id, cuisine_id, 
author_id, status, created_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())
");
$gallery_json = !empty($gallery_images) ? json_encode($gallery_images) : null;
$stmt->execute([
$form_data['title'],
$slug,
$form_data['description'],
$form_data['ingredients'],
$form_data['instructions'],
$form_data['prep_time'],
$form_data['cook_time'],
$form_data['total_time'],
$form_data['servings'],
$form_data['difficulty'],
$form_data['calories'],
$featured_image,
$gallery_json,
$form_data['video_url'],
$form_data['category_id'],
$form_data['cuisine_id'],
$_SESSION['user_id'],
'pending' // New submissions require approval
]);
$recipe_id = $pdo->lastInsertId();
// Insert dietary options
if (!empty($form_data['dietary'])) {
$diet_stmt = $pdo->prepare("INSERT INTO recipe_dietary (recipe_id, dietary_id) VALUES (?, ?)");
foreach ($form_data['dietary'] as $dietary_id) {
$diet_stmt->execute([$recipe_id, $dietary_id]);
}
}
$pdo->commit();
$_SESSION['success'] = 'Your recipe has been submitted successfully and is pending review.';
redirect('recipe.php?slug=' . $slug);
} catch (Exception $e) {
$pdo->rollBack();
$errors['general'] = 'Failed to submit recipe: ' . $e->getMessage();
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Submit Recipe - <?php echo SITE_NAME; ?></title>
<link rel="stylesheet" href="assets/css/style.css">
<script src="assets/vendor/tinymce/tinymce.min.js"></script>
</head>
<body>
<!-- Header -->
<?php include 'includes/header.php'; ?>
<div class="container">
<div class="submit-recipe-container">
<h1 class="page-title">Share Your Recipe</h1>
<p class="page-description">Fill in the details below to share your culinary creation with our community.</p>
<?php if (!empty($errors['general'])): ?>
<div class="alert alert-error"><?php echo $errors['general']; ?></div>
<?php endif; ?>
<?php if (!isLoggedIn()): ?>
<div class="alert alert-info">
Please <a href="login.php">login</a> or <a href="register.php">register</a> to submit a recipe.
</div>
<?php endif; ?>
<form method="POST" action="" enctype="multipart/form-data" class="submit-form">
<!-- Basic Information -->
<div class="form-section">
<h2>Basic Information</h2>
<div class="form-group">
<label for="title">Recipe Title *</label>
<input type="text" id="title" name="title" 
value="<?php echo htmlspecialchars($form_data['title']); ?>" 
required maxlength="200">
<?php if (isset($errors['title'])): ?>
<span class="error"><?php echo $errors['title']; ?></span>
<?php endif; ?>
</div>
<div class="form-group">
<label for="description">Description *</label>
<textarea id="description" name="description" rows="4" required><?php echo htmlspecialchars($form_data['description']); ?></textarea>
<small>Briefly describe your recipe (will appear in search results)</small>
<?php if (isset($errors['description'])): ?>
<span class="error"><?php echo $errors['description']; ?></span>
<?php endif; ?>
</div>
<div class="form-row">
<div class="form-group col-md-4">
<label for="category_id">Category *</label>
<select id="category_id" name="category_id" required>
<option value="">Select Category</option>
<?php foreach ($categories as $cat): ?>
<option value="<?php echo $cat['id']; ?>" 
<?php echo $form_data['category_id'] == $cat['id'] ? 'selected' : ''; ?>>
<?php echo $cat['name']; ?>
</option>
<?php endforeach; ?>
</select>
<?php if (isset($errors['category_id'])): ?>
<span class="error"><?php echo $errors['category_id']; ?></span>
<?php endif; ?>
</div>
<div class="form-group col-md-4">
<label for="cuisine_id">Cuisine</label>
<select id="cuisine_id" name="cuisine_id">
<option value="">Select Cuisine</option>
<?php foreach ($cuisines as $cuisine): ?>
<option value="<?php echo $cuisine['id']; ?>" 
<?php echo $form_data['cuisine_id'] == $cuisine['id'] ? 'selected' : ''; ?>>
<?php echo $cuisine['name']; ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group col-md-4">
<label for="difficulty">Difficulty</label>
<select id="difficulty" name="difficulty">
<option value="easy" <?php echo $form_data['difficulty'] == 'easy' ? 'selected' : ''; ?>>Easy</option>
<option value="medium" <?php echo $form_data['difficulty'] == 'medium' ? 'selected' : ''; ?>>Medium</option>
<option value="hard" <?php echo $form_data['difficulty'] == 'hard' ? 'selected' : ''; ?>>Hard</option>
</select>
</div>
</div>
</div>
<!-- Time & Servings -->
<div class="form-section">
<h2>Time & Servings</h2>
<div class="form-row">
<div class="form-group col-md-3">
<label for="prep_time">Prep Time (minutes)</label>
<input type="number" id="prep_time" name="prep_time" 
value="<?php echo $form_data['prep_time']; ?>" min="0">
</div>
<div class="form-group col-md-3">
<label for="cook_time">Cook Time (minutes)</label>
<input type="number" id="cook_time" name="cook_time" 
value="<?php echo $form_data['cook_time']; ?>" min="0">
</div>
<div class="form-group col-md-3">
<label for="total_time">Total Time (minutes)</label>
<input type="number" id="total_time" name="total_time" 
value="<?php echo $form_data['total_time']; ?>" min="0">
<small>Leave empty to auto-calculate</small>
</div>
<div class="form-group col-md-3">
<label for="servings">Servings</label>
<input type="number" id="servings" name="servings" 
value="<?php echo $form_data['servings']; ?>" min="1">
</div>
</div>
</div>
<!-- Ingredients -->
<div class="form-section">
<h2>Ingredients *</h2>
<div class="form-group">
<textarea id="ingredients" name="ingredients" rows="8" required><?php echo htmlspecialchars($form_data['ingredients']); ?></textarea>
<small>Enter one ingredient per line. Example:<br>
2 cups all-purpose flour<br>
1 teaspoon salt<br>
3 large eggs</small>
<?php if (isset($errors['ingredients'])): ?>
<span class="error"><?php echo $errors['ingredients']; ?></span>
<?php endif; ?>
</div>
</div>
<!-- Instructions -->
<div class="form-section">
<h2>Instructions *</h2>
<div class="form-group">
<textarea id="instructions" name="instructions" rows="10" required><?php echo htmlspecialchars($form_data['instructions']); ?></textarea>
<small>Enter each step on a new line. Numbering will be added automatically.</small>
<?php if (isset($errors['instructions'])): ?>
<span class="error"><?php echo $errors['instructions']; ?></span>
<?php endif; ?>
</div>
</div>
<!-- Dietary Options -->
<div class="form-section">
<h2>Dietary Options</h2>
<div class="dietary-checkboxes">
<?php foreach ($dietary_options as $diet): ?>
<label class="checkbox-label">
<input type="checkbox" name="dietary[]" value="<?php echo $diet['id']; ?>"
<?php echo in_array($diet['id'], $form_data['dietary']) ? 'checked' : ''; ?>>
<span><?php echo $diet['icon'] . ' ' . $diet['name']; ?></span>
</label>
<?php endforeach; ?>
</div>
</div>
<!-- Media -->
<div class="form-section">
<h2>Media</h2>
<div class="form-group">
<label for="featured_image">Featured Image</label>
<input type="file" id="featured_image" name="featured_image" accept="image/*">
<?php if (isset($errors['featured_image'])): ?>
<span class="error"><?php echo $errors['featured_image']; ?></span>
<?php endif; ?>
<small>Max size: 5MB. Recommended size: 1200x800px</small>
</div>
<div class="form-group">
<label for="gallery_images">Gallery Images (optional)</label>
<input type="file" id="gallery_images" name="gallery_images[]" accept="image/*" multiple>
<small>You can select multiple images. Max 5MB each.</small>
</div>
<div class="form-group">
<label for="video_url">Video URL (YouTube/Vimeo)</label>
<input type="url" id="video_url" name="video_url" 
value="<?php echo htmlspecialchars($form_data['video_url']); ?>"
placeholder="https://www.youtube.com/watch?v=...">
</div>
</div>
<!-- Nutrition (Optional) -->
<div class="form-section">
<h2>Nutrition Information (Optional)</h2>
<div class="form-row">
<div class="form-group col-md-3">
<label for="calories">Calories (per serving)</label>
<input type="number" id="calories" name="calories" 
value="<?php echo $form_data['calories']; ?>" min="0">
</div>
<!-- Add more nutrition fields as needed -->
</div>
</div>
<!-- Submit -->
<div class="form-actions">
<button type="submit" class="btn btn-primary btn-large" <?php echo !isLoggedIn() ? 'disabled' : ''; ?>>
Submit Recipe
</button>
<a href="index.php" class="btn btn-large">Cancel</a>
</div>
</form>
</div>
</div>
<!-- Footer -->
<?php include 'includes/footer.php'; ?>
<script>
// Initialize TinyMCE for instructions
tinymce.init({
selector: '#instructions',
height: 400,
menubar: false,
plugins: [
'advlist autolink lists link image charmap print preview anchor',
'searchreplace visualblocks code fullscreen',
'insertdatetime media table paste code help wordcount'
],
toolbar: 'undo redo | formatselect | bold italic backcolor | \
alignleft aligncenter alignright alignjustify | \
bullist numlist outdent indent | removeformat | help'
});
// Auto-calculate total time
document.getElementById('prep_time').addEventListener('input', calculateTotal);
document.getElementById('cook_time').addEventListener('input', calculateTotal);
function calculateTotal() {
const prep = parseInt(document.getElementById('prep_time').value) || 0;
const cook = parseInt(document.getElementById('cook_time').value) || 0;
const total = prep + cook;
if (total > 0) {
document.getElementById('total_time').value = total;
}
}
</script>
</body>
</html>

6. Admin Dashboard (admin/dashboard.php)

<?php
require_once '../includes/config.php';
require_once '../includes/auth.php';
Auth::checkModerator();
// Get statistics
$totalRecipes = $pdo->query("SELECT COUNT(*) FROM recipes")->fetchColumn();
$pendingRecipes = $pdo->query("SELECT COUNT(*) FROM recipes WHERE status = 'pending'")->fetchColumn();
$approvedRecipes = $pdo->query("SELECT COUNT(*) FROM recipes WHERE status = 'approved'")->fetchColumn();
$featuredRecipes = $pdo->query("SELECT COUNT(*) FROM recipes WHERE status = 'featured'")->fetchColumn();
$totalUsers = $pdo->query("SELECT COUNT(*) FROM users")->fetchColumn();
$newUsers = $pdo->query("SELECT COUNT(*) FROM users WHERE DATE(created_at) = CURDATE()")->fetchColumn();
$totalComments = $pdo->query("SELECT COUNT(*) FROM comments")->fetchColumn();
$pendingComments = $pdo->query("SELECT COUNT(*) FROM comments WHERE status = 'pending'")->fetchColumn();
$totalViews = $pdo->query("SELECT SUM(views) FROM recipes")->fetchColumn();
// Get recent recipes
$recentRecipes = $pdo->query("
SELECT r.*, u.username as author_name, c.name as category_name
FROM recipes r
JOIN users u ON r.author_id = u.id
JOIN categories c ON r.category_id = c.id
ORDER BY r.created_at DESC
LIMIT 5
")->fetchAll();
// Get pending recipes for quick moderation
$pendingList = $pdo->query("
SELECT r.*, u.username as author_name
FROM recipes r
JOIN users u ON r.author_id = u.id
WHERE r.status = 'pending'
ORDER BY r.created_at ASC
LIMIT 5
")->fetchAll();
// Get recent comments
$recentComments = $pdo->query("
SELECT c.*, r.title as recipe_title, u.username as author_name
FROM comments c
JOIN recipes r ON c.recipe_id = r.id
LEFT JOIN users u ON c.user_id = u.id
ORDER BY c.created_at DESC
LIMIT 5
")->fetchAll();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Dashboard - <?php echo SITE_NAME; ?></title>
<link rel="stylesheet" href="../assets/css/admin.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
<div class="admin-wrapper">
<!-- Sidebar -->
<aside class="sidebar">
<div class="sidebar-header">
<h2>Recipe Admin</h2>
<p>Welcome, <?php echo $_SESSION['user_name']; ?></p>
</div>
<nav class="sidebar-nav">
<ul>
<li><a href="dashboard.php" class="active">Dashboard</a></li>
<li><a href="recipes.php">All Recipes</a></li>
<li><a href="pending-recipes.php">Pending Recipes 
<?php if ($pendingRecipes > 0): ?>
<span class="badge"><?php echo $pendingRecipes; ?></span>
<?php endif; ?>
</a></li>
<li><a href="categories.php">Categories</a></li>
<li><a href="cuisines.php">Cuisines</a></li>
<li><a href="dietary.php">Dietary Options</a></li>
<li><a href="comments.php">Comments 
<?php if ($pendingComments > 0): ?>
<span class="badge"><?php echo $pendingComments; ?></span>
<?php endif; ?>
</a></li>
<li><a href="users.php">Users</a></li>
<li><a href="featured.php">Featured Recipes</a></li>
<li><a href="settings.php">Settings</a></li>
<li><a href="newsletter.php">Newsletter</a></li>
<li><a href="logout.php">Logout</a></li>
</ul>
</nav>
</aside>
<!-- Main Content -->
<main class="admin-main">
<div class="container">
<h1>Dashboard</h1>
<!-- Stats Cards -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon">🍳</div>
<div class="stat-details">
<h3><?php echo $totalRecipes; ?></h3>
<p>Total Recipes</p>
<small><?php echo $approvedRecipes; ?> approved, <?php echo $pendingRecipes; ?> pending</small>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">πŸ‘₯</div>
<div class="stat-details">
<h3><?php echo $totalUsers; ?></h3>
<p>Total Users</p>
<small><?php echo $newUsers; ?> new today</small>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">πŸ’¬</div>
<div class="stat-details">
<h3><?php echo $totalComments; ?></h3>
<p>Comments</p>
<?php if ($pendingComments > 0): ?>
<small class="pending"><?php echo $pendingComments; ?> pending</small>
<?php endif; ?>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">πŸ‘οΈ</div>
<div class="stat-details">
<h3><?php echo number_format($totalViews ?: 0); ?></h3>
<p>Total Views</p>
</div>
</div>
</div>
<!-- Quick Actions -->
<div class="quick-actions">
<a href="pending-recipes.php" class="action-card">
<span class="action-icon">⏳</span>
<span>Moderate Recipes</span>
<?php if ($pendingRecipes > 0): ?>
<span class="badge"><?php echo $pendingRecipes; ?></span>
<?php endif; ?>
</a>
<a href="comments.php?filter=pending" class="action-card">
<span class="action-icon">πŸ’¬</span>
<span>Moderate Comments</span>
<?php if ($pendingComments > 0): ?>
<span class="badge"><?php echo $pendingComments; ?></span>
<?php endif; ?>
</a>
<a href="featured.php" class="action-card">
<span class="action-icon">🌟</span>
<span>Manage Featured</span>
</a>
<a href="newsletter.php" class="action-card">
<span class="action-icon">πŸ“§</span>
<span>Send Newsletter</span>
</a>
</div>
<div class="dashboard-grid">
<!-- Pending Recipes -->
<?php if (!empty($pendingList)): ?>
<div class="card">
<div class="card-header">
<h3>Pending Recipes</h3>
<a href="pending-recipes.php" class="btn btn-sm">View All</a>
</div>
<div class="card-body">
<table class="data-table">
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>Submitted</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($pendingList as $recipe): ?>
<tr>
<td><?php echo $recipe['title']; ?></td>
<td><?php echo $recipe['author_name']; ?></td>
<td><?php echo timeAgo($recipe['created_at']); ?></td>
<td>
<a href="edit-recipe.php?id=<?php echo $recipe['id']; ?>" class="btn-small">View</a>
<a href="?approve=<?php echo $recipe['id']; ?>" class="btn-small btn-success">Approve</a>
<a href="?reject=<?php echo $recipe['id']; ?>" class="btn-small btn-danger">Reject</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php endif; ?>
<!-- Recent Recipes -->
<div class="card">
<div class="card-header">
<h3>Recent Recipes</h3>
<a href="recipes.php" class="btn btn-sm">View All</a>
</div>
<div class="card-body">
<table class="data-table">
<thead>
<tr>
<th>Title</th>
<th>Category</th>
<th>Author</th>
<th>Views</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($recentRecipes as $recipe): ?>
<tr>
<td><?php echo $recipe['title']; ?></td>
<td><?php echo $recipe['category_name']; ?></td>
<td><?php echo $recipe['author_name']; ?></td>
<td><?php echo number_format($recipe['views']); ?></td>
<td>
<span class="badge badge-<?php echo $recipe['status']; ?>">
<?php echo $recipe['status']; ?>
</span>
</td>
<td>
<a href="edit-recipe.php?id=<?php echo $recipe['id']; ?>" class="btn-small">Edit</a>
<a href="../recipe.php?slug=<?php echo $recipe['slug']; ?>" class="btn-small" target="_blank">View</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<!-- Recent Comments -->
<div class="card">
<div class="card-header">
<h3>Recent Comments</h3>
<a href="comments.php" class="btn btn-sm">View All</a>
</div>
<div class="card-body">
<table class="data-table">
<thead>
<tr>
<th>Comment</th>
<th>Recipe</th>
<th>Author</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($recentComments as $comment): ?>
<tr>
<td><?php echo substr($comment['content'], 0, 50); ?>...</td>
<td><?php echo $comment['recipe_title']; ?></td>
<td><?php echo $comment['author_name'] ?: $comment['author_name']; ?></td>
<td>
<span class="badge badge-<?php echo $comment['status']; ?>">
<?php echo $comment['status']; ?>
</span>
</td>
<td>
<?php if ($comment['status'] == 'pending'): ?>
<a href="?approve-comment=<?php echo $comment['id']; ?>" class="btn-small">Approve</a>
<?php endif; ?>
<a href="?delete-comment=<?php echo $comment['id']; ?>" class="btn-small btn-danger">Delete</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</main>
</div>
<script src="../assets/js/admin.js"></script>
</body>
</html>

7. CSS Styling (assets/css/style.css)

/* Additional styles for Recipe Sharing Website */
/* Recipe Page Styles */
.recipe-page {
max-width: 1200px;
margin: 2rem auto;
}
.recipe-header {
margin-bottom: 2rem;
}
.recipe-title {
font-size: 2.5rem;
color: #2c3e50;
margin-bottom: 1rem;
}
.recipe-meta-top {
display: flex;
flex-wrap: wrap;
gap: 2rem;
margin-bottom: 1.5rem;
padding: 1rem;
background: #f8f9fa;
border-radius: 8px;
}
.meta-item {
display: flex;
align-items: center;
gap: 0.5rem;
}
.meta-label {
font-weight: 600;
color: #666;
}
.meta-value {
color: #333;
}
.mini-avatar {
width: 30px;
height: 30px;
border-radius: 50%;
margin-right: 0.5rem;
vertical-align: middle;
}
.recipe-actions {
display: flex;
gap: 1rem;
align-items: center;
flex-wrap: wrap;
}
.share-buttons {
display: flex;
gap: 0.5rem;
margin-left: auto;
}
.share-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border-radius: 50%;
color: #fff;
text-decoration: none;
font-weight: bold;
transition: opacity 0.3s;
}
.share-btn:hover {
opacity: 0.8;
}
.share-btn.facebook { background: #3b5998; }
.share-btn.twitter { background: #1da1f2; }
.share-btn.pinterest { background: #bd081c; }
.recipe-content-wrapper {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 2rem;
margin-bottom: 3rem;
}
.recipe-featured-image {
margin-bottom: 2rem;
border-radius: 8px;
overflow: hidden;
}
.recipe-featured-image img {
width: 100%;
height: auto;
display: block;
}
.recipe-description {
margin-bottom: 2rem;
}
.recipe-description h2 {
color: #2c3e50;
margin-bottom: 1rem;
font-size: 1.5rem;
}
.recipe-info-box {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 1rem;
background: #f8f9fa;
padding: 1.5rem;
border-radius: 8px;
margin-bottom: 2rem;
}
.info-item {
text-align: center;
}
.info-icon {
font-size: 2rem;
display: block;
margin-bottom: 0.5rem;
}
.info-label {
display: block;
font-size: 0.9rem;
color: #666;
margin-bottom: 0.25rem;
}
.info-value {
display: block;
font-weight: 600;
color: #2c3e50;
}
.dietary-tags-section {
margin-bottom: 2rem;
}
.dietary-tags-section h2 {
color: #2c3e50;
margin-bottom: 1rem;
font-size: 1.5rem;
}
.dietary-tags {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.diet-tag {
background: #e8f4f8;
color: #2c3e50;
padding: 0.5rem 1rem;
border-radius: 20px;
font-size: 0.9rem;
text-decoration: none;
transition: background 0.3s;
}
.diet-tag:hover {
background: #d1e6f0;
}
.ingredients-section {
margin-bottom: 2rem;
background: #f8f9fa;
padding: 2rem;
border-radius: 8px;
}
.ingredients-section h2 {
color: #2c3e50;
margin-bottom: 1.5rem;
font-size: 1.5rem;
}
.ingredients-list {
list-style: none;
}
.ingredient-item {
display: flex;
align-items: center;
padding: 0.5rem 0;
border-bottom: 1px solid #dee2e6;
}
.ingredient-item:last-child {
border-bottom: none;
}
.ingredient-item input[type="checkbox"] {
margin-right: 1rem;
width: 18px;
height: 18px;
cursor: pointer;
}
.ingredient-item label {
cursor: pointer;
flex: 1;
}
.ingredient-item input[type="checkbox"]:checked + label {
text-decoration: line-through;
color: #999;
}
.instructions-section {
margin-bottom: 2rem;
}
.instructions-section h2 {
color: #2c3e50;
margin-bottom: 1.5rem;
font-size: 1.5rem;
}
.instructions-list {
counter-reset: step-counter;
}
.instruction-step {
display: flex;
gap: 1rem;
margin-bottom: 1.5rem;
padding: 1rem;
background: #f8f9fa;
border-radius: 8px;
}
.step-number {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
background: #3498db;
color: #fff;
border-radius: 50%;
font-weight: bold;
flex-shrink: 0;
}
.instruction-step p {
flex: 1;
line-height: 1.6;
}
.video-section {
margin-bottom: 2rem;
}
.video-section h2 {
color: #2c3e50;
margin-bottom: 1rem;
font-size: 1.5rem;
}
.video-container {
position: relative;
padding-bottom: 56.25%;
height: 0;
overflow: hidden;
}
.video-container iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.gallery-section {
margin-bottom: 2rem;
}
.gallery-section h2 {
color: #2c3e50;
margin-bottom: 1rem;
font-size: 1.5rem;
}
.gallery-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 1rem;
}
.gallery-item {
display: block;
border-radius: 8px;
overflow: hidden;
transition: transform 0.3s;
}
.gallery-item:hover {
transform: scale(1.05);
}
.gallery-item img {
width: 100%;
height: 150px;
object-fit: cover;
display: block;
}
/* Recipe Sidebar */
.author-box {
background: #fff;
padding: 2rem;
border-radius: 8px;
text-align: center;
margin-bottom: 1.5rem;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.author-avatar-large {
width: 100px;
height: 100px;
border-radius: 50%;
margin-bottom: 1rem;
object-fit: cover;
}
.author-box h3 {
color: #2c3e50;
margin-bottom: 0.25rem;
}
.author-username {
color: #999;
font-size: 0.9rem;
margin-bottom: 1rem;
}
.author-bio {
color: #666;
font-size: 0.9rem;
margin-bottom: 1.5rem;
}
.stats-box {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
background: #fff;
padding: 1.5rem;
border-radius: 8px;
margin-bottom: 1.5rem;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.stat-item {
text-align: center;
}
.stat-icon {
font-size: 1.5rem;
display: block;
margin-bottom: 0.5rem;
}
.stat-value {
display: block;
font-size: 1.2rem;
font-weight: bold;
color: #2c3e50;
}
.stat-label {
display: block;
font-size: 0.8rem;
color: #999;
}
.nutrition-box {
background: #fff;
padding: 1.5rem;
border-radius: 8px;
margin-bottom: 1.5rem;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.nutrition-box h3 {
color: #2c3e50;
margin-bottom: 1rem;
font-size: 1.1rem;
}
.nutrition-item {
display: flex;
justify-content: space-between;
padding: 0.5rem 0;
border-bottom: 1px solid #eee;
}
.nutrition-item:last-child {
border-bottom: none;
}
/* Ratings Section */
.ratings-section {
background: #fff;
padding: 2rem;
border-radius: 8px;
margin-bottom: 2rem;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.ratings-section h2 {
color: #2c3e50;
margin-bottom: 1.5rem;
}
.rating-summary {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
margin-bottom: 2rem;
}
.average-rating {
text-align: center;
}
.big-rating {
font-size: 3rem;
font-weight: bold;
color: #2c3e50;
display: block;
margin-bottom: 0.5rem;
}
.rating-stars {
font-size: 1.5rem;
color: #f1c40f;
display: block;
margin-bottom: 0.5rem;
}
.rating-count {
color: #999;
}
.your-rating h3 {
color: #2c3e50;
margin-bottom: 1rem;
}
.star-rating {
display: flex;
flex-direction: row-reverse;
justify-content: flex-end;
gap: 0.25rem;
margin-bottom: 1rem;
}
.star-rating input {
display: none;
}
.star-rating label {
font-size: 2rem;
color: #ddd;
cursor: pointer;
transition: color 0.2s;
}
.star-rating label:hover,
.star-rating label:hover ~ label,
.star-rating input:checked ~ label {
color: #f1c40f;
}
.reviews-list {
display: flex;
flex-direction: column;
gap: 1rem;
}
.review-item {
padding: 1rem;
background: #f8f9fa;
border-radius: 8px;
}
.review-header {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 1rem;
}
.review-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
}
.review-meta {
flex: 1;
}
.review-author {
font-weight: 600;
color: #2c3e50;
display: block;
}
.review-date {
font-size: 0.8rem;
color: #999;
}
.review-rating {
color: #f1c40f;
}
.review-content {
line-height: 1.6;
}
/* Submit Recipe Form */
.submit-recipe-container {
max-width: 900px;
margin: 2rem auto;
}
.submit-form {
background: #fff;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.form-section {
margin-bottom: 2rem;
padding-bottom: 2rem;
border-bottom: 1px solid #eee;
}
.form-section:last-child {
border-bottom: none;
}
.form-section h2 {
color: #2c3e50;
margin-bottom: 1.5rem;
font-size: 1.3rem;
}
.dietary-checkboxes {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 1rem;
}
/* Lightbox Modal */
.lightbox-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.9);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.lightbox-modal .close {
position: absolute;
top: 20px;
right: 30px;
color: #fff;
font-size: 40px;
font-weight: bold;
cursor: pointer;
}
.lightbox-image {
max-width: 90%;
max-height: 90%;
object-fit: contain;
}
/* Responsive */
@media (max-width: 768px) {
.recipe-content-wrapper {
grid-template-columns: 1fr;
}
.rating-summary {
grid-template-columns: 1fr;
}
.recipe-meta-top {
flex-direction: column;
gap: 1rem;
}
.recipe-actions {
flex-direction: column;
align-items: stretch;
}
.share-buttons {
margin-left: 0;
justify-content: center;
}
.gallery-grid {
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
}
}
@media (max-width: 480px) {
.recipe-title {
font-size: 2rem;
}
.recipe-info-box {
grid-template-columns: 1fr 1fr;
}
.dietary-checkboxes {
grid-template-columns: 1fr;
}
}

πŸ“ How to Use This Project (Step-by-Step Guide)

Step 1: Install Local Server

  1. Download and install XAMPP from apachefriends.org
  2. Launch XAMPP Control Panel
  3. Start Apache and MySQL services

Step 2: Create Project Folder

  1. Navigate to C:\xampp\htdocs\ (Windows) or /Applications/XAMPP/htdocs/ (Mac)
  2. Create a new folder named recipe-sharing

Step 3: Set Up Database

  1. Open browser and go to http://localhost/phpmyadmin
  2. Click on "SQL" tab
  3. Copy the entire SQL from database/recipe_db.sql
  4. Paste and click "Go" to create database and tables

Step 4: Create Password Hash for Admin

Create a file named hash.php in project root:

<?php
echo password_hash('admin123', PASSWORD_DEFAULT);
?>

Run it: http://localhost/recipe-sharing/hash.php
Copy the hash and update it in the SQL insert statement for the admin user.

Step 5: Configure Database Connection

Open includes/config.php and verify:

define('DB_HOST', 'localhost');
define('DB_NAME', 'recipe_db');
define('DB_USER', 'root');
define('DB_PASS', '');

Step 6: Create Required Directories

Create these folders and set proper permissions:

  • assets/images/recipes/ - for recipe images
  • assets/images/avatars/ - for user avatars
  • assets/images/uploads/ - for temporary uploads

Step 7: Download Third-Party Libraries

  1. Download TinyMCE from tiny.cloud and place in assets/vendor/tinymce/
  2. Copy the masonry layout library to assets/vendor/masonry/

Step 8: Test the Application

Public Side:

  • Open: http://localhost/recipe-sharing/
  • Browse recipes
  • View recipe details
  • Search for recipes
  • Register a new account

Admin Side:

  • Open: http://localhost/recipe-sharing/admin/login.php
  • Login with: Username: admin, Password: admin123

Step 9: Add Sample Content

  1. Login to admin panel
  2. Go to Categories β†’ Ensure categories exist
  3. Go to Cuisines β†’ Add cuisines
  4. Go to Dietary Options β†’ Add dietary options
  5. Submit a recipe from the public side
  6. Approve the recipe from admin panel
  7. Feature the recipe

🎯 Features Summary

Public User Features:

  • βœ… Browse recipes by categories and cuisines
  • βœ… View detailed recipe pages with ingredients and instructions
  • βœ… Search recipes by title, ingredients, or description
  • βœ… Filter by dietary preferences
  • βœ… Rate and review recipes
  • βœ… Save favorite recipes
  • βœ… Submit own recipes with images
  • βœ… Comment on recipes
  • βœ… Share recipes on social media
  • βœ… Print recipes
  • βœ… Subscribe to newsletter

Admin Features:

  • βœ… Dashboard with statistics
  • βœ… Recipe moderation (approve/reject)
  • βœ… Manage categories, cuisines, dietary options
  • βœ… User management
  • βœ… Comment moderation
  • βœ… Featured recipes management
  • βœ… Newsletter management
  • βœ… Site settings

Technical Features:

  • βœ… Secure password hashing
  • βœ… Session management
  • βœ… SQL injection prevention
  • βœ… XSS protection
  • βœ… SEO-friendly URLs (slugs)
  • βœ… Image upload with validation
  • βœ… Rich text editor for instructions
  • βœ… Responsive design
  • βœ… AJAX search suggestions
  • βœ… Infinite scroll
  • βœ… Lightbox gallery

πŸš€ Future Enhancements

  1. Meal Planning: Allow users to create weekly meal plans
  2. Shopping Lists: Generate shopping lists from recipes
  3. Nutrition Calculator: Calculate detailed nutrition information
  4. Recipe Collections: Create curated collections (e.g., "Quick Meals", "Holiday Recipes")
  5. Social Features: Follow other users, share to social media
  6. Mobile App: React Native or Flutter app
  7. Recipe Scaling: Automatically scale ingredients for different servings
  8. Print-Friendly View: Optimized printing layout
  9. Video Integration: Embed cooking videos
  10. Recipe Collections: Allow users to create and share collections
  11. Meal Planning Calendar: Plan meals for the week
  12. Grocery List: Auto-generate grocery lists from selected recipes
  13. Nutrition Facts: Detailed nutrition calculator
  14. Cooking Mode: Step-by-step guided cooking mode
  15. Recipe Scanner: OCR to scan and import recipes from images
  16. Multi-language Support: Translate recipes
  17. Offline Access: Save recipes for offline viewing
  18. Kitchen Inventory: Track ingredients you have
  19. Recipe Recommendations: AI-powered recommendations
  20. Cooking Timer: Built-in timer for recipes

This comprehensive Recipe Sharing Website provides a complete platform for food lovers to share, discover, and enjoy recipes from around the world!

Leave a Reply

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


Macro Nepal Helper