BLOG CMS (CMS) IN HTML CSS AND JAVASCRIPT WITH PHP AND MY SQL

Introduction

The Blog CMS is a powerful, user-friendly content management system designed specifically for bloggers and content creators. It provides a complete platform for writing, editing, publishing, and managing blog posts, pages, and multimedia content. The system features role-based access control with distinct interfaces for administrators, editors, and authors, making it suitable for both solo bloggers and multi-author blogs. With its intuitive WYSIWYG editor, media management, SEO tools, and analytics, this CMS offers everything needed to run a successful blog.

Project Features

Admin Features:

  • Complete Dashboard with Analytics
  • User Management (Create, Edit, Delete Users)
  • Role Management (Admin, Editor, Author)
  • Post Management (All Posts)
  • Page Management
  • Category Management
  • Tag Management
  • Media Library Management
  • Comment Moderation
  • Plugin Management
  • Theme Management
  • System Settings
  • Backup & Restore
  • SEO Settings
  • Security Settings

Editor Features:

  • Create and Edit Posts
  • Schedule Posts
  • Manage Categories and Tags
  • Moderate Comments
  • View Analytics
  • Manage Media
  • Draft and Revision Management

Author Features:

  • Write and Edit Own Posts
  • Submit for Review
  • View Own Post Statistics
  • Manage Own Profile
  • Upload Media
  • Save Drafts
  • View Revision History

Public Features:

  • View Blog Posts
  • Search Posts
  • Filter by Category/Tag
  • Post Comments
  • Social Sharing
  • RSS Feeds
  • Related Posts
  • Post Views Counter
  • Author Archives
  • Responsive Design

Project File Structure

blog-cms/
│
├── assets/
│   ├── css/
│   │   ├── style.css
│   │   ├── admin-style.css
│   │   ├── editor-style.css
│   │   └── responsive.css
│   ├── js/
│   │   ├── main.js
│   │   ├── admin.js
│   │   ├── editor.js
│   │   ├── comment.js
│   │   └── media.js
│   ├── images/
│   │   ├── posts/
│   │   ├── avatars/
│   │   └── media/
│   └── vendor/
│       ├── tinymce/
│       ├── font-awesome/
│       └── cropperjs/
│
├── database/
│   └── blog_cms.sql
│
├── includes/
│   ├── config.php
│   ├── db_connection.php
│   ├── functions.php
│   ├── session.php
│   ├── auth.php
│   ├── seo.php
│   └── media.php
│
├── admin/
│   ├── index.php (Dashboard)
│   ├── login.php
│   ├── logout.php
│   ├── profile.php
│   ├── posts/
│   │   ├── index.php
│   │   ├── create.php
│   │   ├── edit.php
│   │   ├── view.php
│   │   ├── delete.php
│   │   └── revisions.php
│   ├── pages/
│   │   ├── index.php
│   │   ├── create.php
│   │   ├── edit.php
│   │   └── delete.php
│   ├── media/
│   │   ├── index.php
│   │   ├── upload.php
│   │   ├── edit.php
│   │   └── delete.php
│   ├── comments/
│   │   ├── index.php
│   │   ├── moderate.php
│   │   └── settings.php
│   ├── categories/
│   │   ├── index.php
│   │   ├── add.php
│   │   ├── edit.php
│   │   └── delete.php
│   ├── tags/
│   │   ├── index.php
│   │   ├── add.php
│   │   ├── edit.php
│   │   └── delete.php
│   ├── users/
│   │   ├── index.php
│   │   ├── add.php
│   │   ├── edit.php
│   │   ├── delete.php
│   │   └── profile.php
│   ├── settings/
│   │   ├── general.php
│   │   ├── reading.php
│   │   ├── writing.php
│   │   ├── discussion.php
│   │   ├── media.php
│   │   ├── permalinks.php
│   │   ├── seo.php
│   │   └── backup.php
│   ├── themes/
│   │   ├── index.php
│   │   ├── customize.php
│   │   └── editor.php
│   └── plugins/
│       ├── index.php
│       └── install.php
│
├── editor/
│   ├── index.php (Dashboard)
│   ├── posts/
│   │   ├── index.php
│   │   ├── create.php
│   │   ├── edit.php
│   │   └── review.php
│   ├── media/
│   │   └── index.php
│   ├── comments/
│   │   └── moderate.php
│   └── analytics/
│       └── index.php
│
├── author/
│   ├── index.php (Dashboard)
│   ├── posts/
│   │   ├── index.php
│   │   ├── create.php
│   │   ├── edit.php
│   │   └── stats.php
│   ├── media/
│   │   └── index.php
│   └── profile.php
│
├── api/
│   ├── upload.php
│   ├── search.php
│   ├── comment.php
│   ├── like.php
│   ├── view-counter.php
│   └── rss-feed.php
│
├── themes/
│   ├── default/
│   │   ├── index.php
│   │   ├── header.php
│   │   ├── footer.php
│   │   ├── sidebar.php
│   │   ├── single.php
│   │   ├── page.php
│   │   ├── archive.php
│   │   ├── search.php
│   │   ├── 404.php
│   │   ├── style.css
│   │   └── functions.php
│   └── custom/
│       └── (custom theme files)
│
├── uploads/
│   ├── posts/
│   ├── pages/
│   ├── media/
│   └── thumbnails/
│
├── index.php (Public Home)
├── single.php
├── page.php
├── category.php
├── tag.php
├── author.php
├── search.php
├── 404.php
├── sitemap.xml
├── robots.txt
├── .htaccess
└── README.md

Database Schema (blog_cms.sql)

-- Create Database
CREATE DATABASE IF NOT EXISTS blog_cms;
USE blog_cms;
-- Table: users
CREATE TABLE users (
user_id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
display_name VARCHAR(100),
first_name VARCHAR(50),
last_name VARCHAR(50),
bio TEXT,
avatar VARCHAR(255),
role ENUM('admin', 'editor', 'author', 'subscriber') DEFAULT 'author',
website VARCHAR(255),
social_links JSON,
user_status ENUM('active', 'inactive', 'banned') DEFAULT 'active',
email_verified BOOLEAN DEFAULT FALSE,
verification_token VARCHAR(100),
reset_token VARCHAR(100),
reset_expiry DATETIME,
last_login DATETIME,
last_ip VARCHAR(45),
registered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_email (email),
INDEX idx_username (username),
INDEX idx_role (role),
INDEX idx_status (user_status)
);
-- Insert default admin
INSERT INTO users (username, email, password, display_name, first_name, last_name, role) VALUES 
('admin', '[email protected]', MD5('Admin@123'), 'Administrator', 'Admin', 'User', 'admin'),
('editor', '[email protected]', MD5('Editor@123'), 'Senior Editor', 'Editor', 'User', 'editor'),
('author', '[email protected]', MD5('Author@123'), 'Content Writer', 'Author', 'User', 'author');
-- Table: categories
CREATE TABLE categories (
category_id INT PRIMARY KEY AUTO_INCREMENT,
category_name VARCHAR(100) NOT NULL,
category_slug VARCHAR(100) UNIQUE NOT NULL,
category_description TEXT,
parent_category_id INT NULL,
category_image VARCHAR(255),
meta_title VARCHAR(255),
meta_description VARCHAR(160),
meta_keywords VARCHAR(255),
display_order INT DEFAULT 0,
post_count INT DEFAULT 0,
is_active BOOLEAN DEFAULT TRUE,
created_by INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (parent_category_id) REFERENCES categories(category_id) ON DELETE CASCADE,
FOREIGN KEY (created_by) REFERENCES users(user_id) ON DELETE SET NULL,
INDEX idx_slug (category_slug),
INDEX idx_parent (parent_category_id),
INDEX idx_active (is_active)
);
-- Insert default categories
INSERT INTO categories (category_name, category_slug, category_description) VALUES
('Technology', 'technology', 'Latest technology news and updates'),
('Lifestyle', 'lifestyle', 'Lifestyle tips and tricks'),
('Travel', 'travel', 'Travel guides and experiences'),
('Food', 'food', 'Delicious recipes and food reviews'),
('Health', 'health', 'Health and wellness advice');
-- Table: tags
CREATE TABLE tags (
tag_id INT PRIMARY KEY AUTO_INCREMENT,
tag_name VARCHAR(50) NOT NULL,
tag_slug VARCHAR(100) UNIQUE NOT NULL,
tag_description VARCHAR(255),
post_count INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_slug (tag_slug)
);
-- Table: posts
CREATE TABLE posts (
post_id INT PRIMARY KEY AUTO_INCREMENT,
author_id INT NOT NULL,
title VARCHAR(255) NOT NULL,
slug VARCHAR(255) UNIQUE NOT NULL,
content LONGTEXT,
excerpt TEXT,
featured_image VARCHAR(255),
status ENUM('draft', 'pending', 'published', 'scheduled', 'trash') DEFAULT 'draft',
comment_status ENUM('open', 'closed', 'disabled') DEFAULT 'open',
ping_status ENUM('open', 'closed') DEFAULT 'open',
post_password VARCHAR(255),
post_type ENUM('post', 'page', 'revision', 'custom') DEFAULT 'post',
view_count INT DEFAULT 0,
like_count INT DEFAULT 0,
comment_count INT DEFAULT 0,
meta_title VARCHAR(255),
meta_description VARCHAR(160),
meta_keywords VARCHAR(255),
canonical_url VARCHAR(255),
og_image VARCHAR(255),
og_title VARCHAR(255),
og_description VARCHAR(255),
twitter_card VARCHAR(50),
is_featured BOOLEAN DEFAULT FALSE,
is_sticky BOOLEAN DEFAULT FALSE,
allow_comments BOOLEAN DEFAULT TRUE,
published_at DATETIME,
scheduled_for DATETIME,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (author_id) REFERENCES users(user_id) ON DELETE CASCADE,
INDEX idx_slug (slug),
INDEX idx_author (author_id),
INDEX idx_status (status),
INDEX idx_published (published_at),
INDEX idx_type (post_type),
FULLTEXT idx_search (title, content, excerpt)
);
-- Table: post_categories
CREATE TABLE post_categories (
post_id INT NOT NULL,
category_id INT NOT NULL,
PRIMARY KEY (post_id, category_id),
FOREIGN KEY (post_id) REFERENCES posts(post_id) ON DELETE CASCADE,
FOREIGN KEY (category_id) REFERENCES categories(category_id) ON DELETE CASCADE
);
-- Table: post_tags
CREATE TABLE post_tags (
post_id INT NOT NULL,
tag_id INT NOT NULL,
PRIMARY KEY (post_id, tag_id),
FOREIGN KEY (post_id) REFERENCES posts(post_id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tags(tag_id) ON DELETE CASCADE
);
-- Table: post_revisions
CREATE TABLE post_revisions (
revision_id INT PRIMARY KEY AUTO_INCREMENT,
post_id INT NOT NULL,
author_id INT,
revision_number INT NOT NULL,
title VARCHAR(255),
content LONGTEXT,
excerpt TEXT,
status VARCHAR(20),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (post_id) REFERENCES posts(post_id) ON DELETE CASCADE,
FOREIGN KEY (author_id) REFERENCES users(user_id) ON DELETE SET NULL,
INDEX idx_post_revisions (post_id, revision_number)
);
-- Table: comments
CREATE TABLE comments (
comment_id INT PRIMARY KEY AUTO_INCREMENT,
post_id INT NOT NULL,
user_id INT NULL,
parent_comment_id INT NULL,
author_name VARCHAR(100),
author_email VARCHAR(100),
author_url VARCHAR(255),
author_ip VARCHAR(45),
content TEXT NOT NULL,
status ENUM('pending', 'approved', 'spam', 'trash') DEFAULT 'pending',
like_count INT DEFAULT 0,
user_agent TEXT,
is_subscribed BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (post_id) REFERENCES posts(post_id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE SET NULL,
FOREIGN KEY (parent_comment_id) REFERENCES comments(comment_id) ON DELETE CASCADE,
INDEX idx_post (post_id),
INDEX idx_status (status),
INDEX idx_email (author_email)
);
-- Table: media
CREATE TABLE media (
media_id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
post_id INT NULL,
file_name VARCHAR(255) NOT NULL,
file_path VARCHAR(500) NOT NULL,
file_type VARCHAR(100),
file_size INT,
mime_type VARCHAR(100),
width INT,
height INT,
alt_text VARCHAR(255),
title VARCHAR(255),
caption TEXT,
description TEXT,
attached_to INT NULL,
is_featured BOOLEAN DEFAULT FALSE,
download_count INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE,
FOREIGN KEY (post_id) REFERENCES posts(post_id) ON DELETE SET NULL,
INDEX idx_post_media (post_id),
INDEX idx_user (user_id),
INDEX idx_type (file_type)
);
-- Table: post_meta
CREATE TABLE post_meta (
meta_id INT PRIMARY KEY AUTO_INCREMENT,
post_id INT NOT NULL,
meta_key VARCHAR(255) NOT NULL,
meta_value LONGTEXT,
FOREIGN KEY (post_id) REFERENCES posts(post_id) ON DELETE CASCADE,
INDEX idx_post_meta (post_id, meta_key)
);
-- Table: user_meta
CREATE TABLE user_meta (
meta_id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
meta_key VARCHAR(255) NOT NULL,
meta_value LONGTEXT,
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE,
INDEX idx_user_meta (user_id, meta_key)
);
-- Table: options (settings)
CREATE TABLE options (
option_id INT PRIMARY KEY AUTO_INCREMENT,
option_name VARCHAR(255) UNIQUE NOT NULL,
option_value LONGTEXT,
autoload ENUM('yes', 'no') DEFAULT 'yes',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_name (option_name)
);
-- Insert default options
INSERT INTO options (option_name, option_value) VALUES
('site_title', 'My Blog CMS'),
('site_description', 'A powerful blog content management system'),
('site_keywords', 'blog, cms, content management'),
('site_url', 'http://localhost/blog-cms/'),
('admin_email', '[email protected]'),
('posts_per_page', '10'),
('date_format', 'F j, Y'),
('time_format', 'g:i a'),
('timezone', 'UTC'),
('language', 'en_US'),
('comments_per_page', '50'),
('comment_moderation', '1'),
('comment_whitelist', '0'),
('comment_blacklist', ''),
('comment_max_links', '2'),
('moderation_keys', ''),
('disallowed_keys', ''),
('default_category', '1'),
('default_post_format', 'standard'),
('mail_server', ''),
('mail_port', '587'),
('mail_username', ''),
('mail_password', ''),
('mail_encryption', 'tls'),
('seo_enabled', '1'),
('analytics_code', ''),
('social_links', '{"facebook":"","twitter":"","instagram":"","linkedin":""}'),
('maintenance_mode', '0'),
('theme', 'default'),
('permalinks', '/%year%/%monthnum%/%day%/%postname%/'),
('uploads_organize', '1'),
('image_sizes', '{"thumbnail":{"width":150,"height":150,"crop":true},"medium":{"width":300,"height":300,"crop":false},"large":{"width":1024,"height":1024,"crop":false}}');
-- Table: widgets
CREATE TABLE widgets (
widget_id INT PRIMARY KEY AUTO_INCREMENT,
widget_name VARCHAR(100) NOT NULL,
widget_area VARCHAR(100),
widget_settings JSON,
display_order INT DEFAULT 0,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- Table: menus
CREATE TABLE menus (
menu_id INT PRIMARY KEY AUTO_INCREMENT,
menu_name VARCHAR(100) NOT NULL,
menu_location VARCHAR(100),
menu_items JSON,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Table: subscriptions
CREATE TABLE subscriptions (
subscription_id INT PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(100) UNIQUE NOT NULL,
name VARCHAR(100),
status ENUM('active', 'inactive', 'bounced') DEFAULT 'active',
subscribed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
confirmed_at DATETIME,
unsubscribed_at DATETIME,
confirmation_token VARCHAR(100),
INDEX idx_email (email),
INDEX idx_status (status)
);
-- Table: notifications
CREATE TABLE notifications (
notification_id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
type VARCHAR(50),
title VARCHAR(255),
message TEXT,
link VARCHAR(255),
is_read BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE,
INDEX idx_user (user_id, is_read)
);
-- Table: activity_logs
CREATE TABLE activity_logs (
log_id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
action VARCHAR(100) NOT NULL,
entity_type VARCHAR(50),
entity_id INT,
details JSON,
ip_address VARCHAR(45),
user_agent TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE SET NULL,
INDEX idx_user_activity (user_id),
INDEX idx_date (created_at)
);
-- Table: post_views
CREATE TABLE post_views (
view_id INT PRIMARY KEY AUTO_INCREMENT,
post_id INT NOT NULL,
user_id INT NULL,
ip_address VARCHAR(45),
user_agent TEXT,
viewed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (post_id) REFERENCES posts(post_id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE SET NULL,
INDEX idx_post_views (post_id, viewed_at)
);
-- Table: post_likes
CREATE TABLE post_likes (
like_id INT PRIMARY KEY AUTO_INCREMENT,
post_id INT NOT NULL,
user_id INT NOT NULL,
liked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (post_id) REFERENCES posts(post_id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE,
UNIQUE KEY unique_like (post_id, user_id)
);
-- Create indexes for performance
CREATE INDEX idx_post_date ON posts(published_at);
CREATE INDEX idx_post_author_status ON posts(author_id, status);
CREATE INDEX idx_comment_post_status ON comments(post_id, status);
CREATE INDEX idx_media_post ON media(post_id);
CREATE INDEX idx_revision_post ON post_revisions(post_id);

Core PHP Files

1. includes/config.php

<?php
// Database configuration
define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASS', '');
define('DB_NAME', 'blog_cms');
// Application configuration
define('SITE_NAME', 'Blog CMS');
define('SITE_URL', 'http://localhost/blog-cms/');
define('ADMIN_URL', SITE_URL . 'admin/');
define('EDITOR_URL', SITE_URL . 'editor/');
define('AUTHOR_URL', SITE_URL . 'author/');
// Paths
define('ROOT_PATH', $_SERVER['DOCUMENT_ROOT'] . '/blog-cms/');
define('INCLUDES_PATH', ROOT_PATH . 'includes/');
define('UPLOAD_PATH', ROOT_PATH . 'uploads/');
define('THEMES_PATH', ROOT_PATH . 'themes/');
define('CURRENT_THEME', 'default');
// Upload settings
define('MAX_FILE_SIZE', 10485760); // 10MB
define('ALLOWED_IMAGE_TYPES', 'jpg,jpeg,png,gif,webp');
define('ALLOWED_DOC_TYPES', 'pdf,doc,docx,txt');
define('IMAGE_QUALITY', 80);
// Pagination settings
define('POSTS_PER_PAGE', 10);
define('MAX_PAGE_LINKS', 5);
// Security settings
define('SESSION_TIMEOUT', 3600); // 1 hour
define('MAX_LOGIN_ATTEMPTS', 5);
define('LOCKOUT_TIME', 900); // 15 minutes
define('BCRYPT_COST', 10);
define('CSRF_TOKEN_NAME', 'csrf_token');
// Cache settings
define('CACHE_ENABLED', false);
define('CACHE_DIR', ROOT_PATH . 'cache/');
define('CACHE_TIME', 3600); // 1 hour
// SEO settings
define('SEO_ENABLED', true);
define('SITEMAP_ENABLED', true);
define('SITEMAP_PATH', ROOT_PATH . 'sitemap.xml');
// Start session if not started
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
// Error reporting (disable in production)
error_reporting(E_ALL);
ini_set('display_errors', 1);
// Timezone setting
date_default_timezone_set('UTC');
?>

2. includes/db_connection.php

<?php
require_once 'config.php';
class Database {
private $connection;
private static $instance = null;
private $query_count = 0;
private $queries = [];
private function __construct() {
$this->connect();
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new Database();
}
return self::$instance;
}
private function connect() {
$this->connection = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
if ($this->connection->connect_error) {
die("Connection failed: " . $this->connection->connect_error);
}
$this->connection->set_charset("utf8mb4");
}
public function getConnection() {
return $this->connection;
}
public function escapeString($string) {
return $this->connection->real_escape_string(trim($string));
}
public function prepare($sql) {
$this->query_count++;
if (CACHE_ENABLED && defined('DEBUG_MODE') && DEBUG_MODE) {
$this->queries[] = $sql;
}
return $this->connection->prepare($sql);
}
public function query($sql) {
$this->query_count++;
if (CACHE_ENABLED && defined('DEBUG_MODE') && DEBUG_MODE) {
$this->queries[] = $sql;
}
return $this->connection->query($sql);
}
public function getLastInsertId() {
return $this->connection->insert_id;
}
public function affectedRows() {
return $this->connection->affected_rows;
}
public function getQueryCount() {
return $this->query_count;
}
public function getQueries() {
return $this->queries;
}
public function beginTransaction() {
$this->connection->begin_transaction();
}
public function commit() {
$this->connection->commit();
}
public function rollback() {
$this->connection->rollback();
}
public function __destruct() {
if ($this->connection) {
$this->connection->close();
}
}
}
// Global database instance
$db = Database::getInstance();
$conn = $db->getConnection();
?>

3. includes/functions.php

<?php
require_once 'db_connection.php';
// Redirect to specified page
function redirect($url) {
header("Location: $url");
exit();
}
// Check if user is logged in
function isLoggedIn() {
return isset($_SESSION['user_id']) && !empty($_SESSION['user_id']);
}
// Check user role
function hasRole($role) {
return isset($_SESSION['user_role']) && $_SESSION['user_role'] === $role;
}
// Check if user has permission
function hasPermission($permission) {
$permissions = [
'admin' => ['all'],
'editor' => ['create_post', 'edit_post', 'publish_post', 'manage_categories', 'manage_comments', 'view_analytics'],
'author' => ['create_post', 'edit_own_post', 'upload_media']
];
$user_role = $_SESSION['user_role'] ?? 'guest';
if ($user_role === 'admin') return true;
if (!isset($permissions[$user_role])) return false;
return in_array($permission, $permissions[$user_role]) || in_array('all', $permissions[$user_role]);
}
// Get current user ID
function getCurrentUserId() {
return $_SESSION['user_id'] ?? 0;
}
// Get current user role
function getCurrentUserRole() {
return $_SESSION['user_role'] ?? '';
}
// Format date
function formatDate($date, $format = null) {
if (!$date) return '';
$format = $format ?? getOption('date_format', 'F j, Y');
return date($format, strtotime($date));
}
// Format datetime
function formatDateTime($datetime) {
$date_format = getOption('date_format', 'F j, Y');
$time_format = getOption('time_format', 'g:i a');
return date($date_format . ' ' . $time_format, strtotime($datetime));
}
// Create slug from string
function createSlug($string) {
$string = strtolower($string);
$string = preg_replace('/[^a-z0-9-]/', '-', $string);
$string = preg_replace('/-+/', '-', $string);
return trim($string, '-');
}
// Get unique slug
function getUniqueSlug($title, $table = 'posts', $id = null) {
global $conn;
$slug = createSlug($title);
$original_slug = $slug;
$counter = 1;
$sql = "SELECT COUNT(*) as count FROM $table WHERE slug = ?";
$params = [$slug];
$types = "s";
if ($id) {
$sql .= " AND post_id != ?";
$params[] = $id;
$types .= "i";
}
$stmt = $conn->prepare($sql);
$stmt->bind_param($types, ...$params);
$stmt->execute();
$result = $stmt->get_result();
$count = $result->fetch_assoc()['count'];
while ($count > 0) {
$slug = $original_slug . '-' . $counter;
$params[0] = $slug;
$stmt = $conn->prepare($sql);
$stmt->bind_param($types, ...$params);
$stmt->execute();
$result = $stmt->get_result();
$count = $result->fetch_assoc()['count'];
$counter++;
}
return $slug;
}
// Get option value
function getOption($key, $default = null) {
global $conn;
// Try cache first
static $options_cache = [];
if (isset($options_cache[$key])) {
return $options_cache[$key];
}
$sql = "SELECT option_value FROM options WHERE option_name = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("s", $key);
$stmt->execute();
$result = $stmt->get_result();
if ($row = $result->fetch_assoc()) {
$value = $row['option_value'];
// Try to decode JSON
$decoded = json_decode($value, true);
if (json_last_error() === JSON_ERROR_NONE) {
$options_cache[$key] = $decoded;
return $decoded;
}
$options_cache[$key] = $value;
return $value;
}
return $default;
}
// Update option
function updateOption($key, $value) {
global $conn;
if (is_array($value) || is_object($value)) {
$value = json_encode($value);
}
$sql = "INSERT INTO options (option_name, option_value) VALUES (?, ?) 
ON DUPLICATE KEY UPDATE option_value = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("sss", $key, $value, $value);
return $stmt->execute();
}
// Get posts with pagination
function getPosts($args = []) {
global $conn;
$defaults = [
'post_type' => 'post',
'status' => 'published',
'category_id' => null,
'tag_id' => null,
'author_id' => null,
'search' => '',
'page' => 1,
'per_page' => POSTS_PER_PAGE,
'orderby' => 'published_at',
'order' => 'DESC'
];
$params = array_merge($defaults, $args);
$offset = ($params['page'] - 1) * $params['per_page'];
$sql = "SELECT SQL_CALC_FOUND_ROWS p.*, u.display_name as author_name,
GROUP_CONCAT(DISTINCT c.category_name) as categories,
GROUP_CONCAT(DISTINCT t.tag_name) as tags
FROM posts p
LEFT JOIN users u ON p.author_id = u.user_id
LEFT JOIN post_categories pc ON p.post_id = pc.post_id
LEFT JOIN categories c ON pc.category_id = c.category_id
LEFT JOIN post_tags pt ON p.post_id = pt.post_id
LEFT JOIN tags t ON pt.tag_id = t.tag_id
WHERE p.post_type = ? AND p.status = ?";
$where_params = [$params['post_type'], $params['status']];
$types = "ss";
if ($params['category_id']) {
$sql .= " AND pc.category_id = ?";
$where_params[] = $params['category_id'];
$types .= "i";
}
if ($params['tag_id']) {
$sql .= " AND pt.tag_id = ?";
$where_params[] = $params['tag_id'];
$types .= "i";
}
if ($params['author_id']) {
$sql .= " AND p.author_id = ?";
$where_params[] = $params['author_id'];
$types .= "i";
}
if (!empty($params['search'])) {
$sql .= " AND (p.title LIKE ? OR p.content LIKE ? OR p.excerpt LIKE ?)";
$search_term = "%{$params['search']}%";
$where_params[] = $search_term;
$where_params[] = $search_term;
$where_params[] = $search_term;
$types .= "sss";
}
$sql .= " GROUP BY p.post_id ORDER BY p.{$params['orderby']} {$params['order']} 
LIMIT ? OFFSET ?";
$where_params[] = $params['per_page'];
$where_params[] = $offset;
$types .= "ii";
$stmt = $conn->prepare($sql);
$stmt->bind_param($types, ...$where_params);
$stmt->execute();
$posts = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
// Get total count
$result = $conn->query("SELECT FOUND_ROWS() as total");
$total = $result->fetch_assoc()['total'];
return [
'posts' => $posts,
'total' => $total,
'pages' => ceil($total / $params['per_page']),
'current_page' => $params['page']
];
}
// Get single post by slug or ID
function getPost($identifier, $by = 'slug') {
global $conn;
$sql = "SELECT p.*, u.display_name as author_name, u.user_id as author_id,
u.avatar as author_avatar, u.bio as author_bio
FROM posts p
LEFT JOIN users u ON p.author_id = u.user_id
WHERE p.{$by} = ? AND p.status = 'published'";
$stmt = $conn->prepare($sql);
$stmt->bind_param("s", $identifier);
$stmt->execute();
$result = $stmt->get_result();
if ($post = $result->fetch_assoc()) {
// Get categories
$sql = "SELECT c.* FROM categories c
JOIN post_categories pc ON c.category_id = pc.category_id
WHERE pc.post_id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $post['post_id']);
$stmt->execute();
$post['categories'] = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
// Get tags
$sql = "SELECT t.* FROM tags t
JOIN post_tags pt ON t.tag_id = pt.tag_id
WHERE pt.post_id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $post['post_id']);
$stmt->execute();
$post['tags'] = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
// Get meta
$sql = "SELECT meta_key, meta_value FROM post_meta WHERE post_id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $post['post_id']);
$stmt->execute();
$meta = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
$post['meta'] = [];
foreach ($meta as $m) {
$post['meta'][$m['meta_key']] = $m['meta_value'];
}
}
return $post ?? null;
}
// Increment post view count
function incrementPostView($post_id) {
global $conn;
// Check if already viewed in this session
$session_key = 'viewed_post_' . $post_id;
if (isset($_SESSION[$session_key])) {
return;
}
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
$user_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
$user_id = getCurrentUserId() ?: null;
$sql = "INSERT INTO post_views (post_id, user_id, ip_address, user_agent) 
VALUES (?, ?, ?, ?)";
$stmt = $conn->prepare($sql);
$stmt->bind_param("iiss", $post_id, $user_id, $ip, $user_agent);
$stmt->execute();
// Update post view count
$sql = "UPDATE posts SET view_count = view_count + 1 WHERE post_id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $post_id);
$stmt->execute();
$_SESSION[$session_key] = true;
}
// Get comments for a post
function getComments($post_id, $status = 'approved', $page = 1, $per_page = 50) {
global $conn;
$offset = ($page - 1) * $per_page;
$sql = "SELECT c.*, u.display_name as user_name, u.avatar as user_avatar
FROM comments c
LEFT JOIN users u ON c.user_id = u.user_id
WHERE c.post_id = ? AND c.status = ? AND c.parent_comment_id IS NULL
ORDER BY c.created_at DESC
LIMIT ? OFFSET ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("isii", $post_id, $status, $per_page, $offset);
$stmt->execute();
$comments = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
// Get replies for each comment
foreach ($comments as &$comment) {
$sql = "SELECT c.*, u.display_name as user_name, u.avatar as user_avatar
FROM comments c
LEFT JOIN users u ON c.user_id = u.user_id
WHERE c.parent_comment_id = ? AND c.status = ?
ORDER BY c.created_at ASC";
$stmt = $conn->prepare($sql);
$stmt->bind_param("is", $comment['comment_id'], $status);
$stmt->execute();
$comment['replies'] = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
}
return $comments;
}
// Add comment
function addComment($post_id, $data) {
global $conn;
$user_id = getCurrentUserId() ?: null;
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
$user_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
// Check if comment moderation is enabled
$moderation = getOption('comment_moderation', '1');
$status = $moderation ? 'pending' : 'approved';
$sql = "INSERT INTO comments (post_id, user_id, parent_comment_id, author_name, 
author_email, author_url, content, author_ip, user_agent, status) 
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
$stmt = $conn->prepare($sql);
$stmt->bind_param("iiisssssss", 
$post_id, $user_id, $data['parent_id'], $data['name'],
$data['email'], $data['url'], $data['content'], $ip, $user_agent, $status
);
if ($stmt->execute()) {
$comment_id = $conn->insert_id;
// Update comment count
$sql = "UPDATE posts SET comment_count = comment_count + 1 WHERE post_id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $post_id);
$stmt->execute();
// Send notification to post author
notifyComment($post_id, $comment_id);
return ['success' => true, 'comment_id' => $comment_id, 'status' => $status];
}
return ['success' => false, 'error' => 'Failed to add comment'];
}
// Get categories
function getCategories($args = []) {
global $conn;
$defaults = [
'parent_id' => null,
'hide_empty' => true,
'orderby' => 'display_order',
'order' => 'ASC'
];
$params = array_merge($defaults, $args);
$sql = "SELECT * FROM categories WHERE is_active = 1";
if ($params['parent_id'] !== null) {
$sql .= " AND parent_category_id " . ($params['parent_id'] ? "= {$params['parent_id']}" : "IS NULL");
}
if ($params['hide_empty']) {
$sql .= " AND post_count > 0";
}
$sql .= " ORDER BY {$params['orderby']} {$params['order']}";
$result = $conn->query($sql);
$categories = $result->fetch_all(MYSQLI_ASSOC);
// Get child categories
foreach ($categories as &$category) {
$sql = "SELECT COUNT(*) as count FROM categories WHERE parent_category_id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $category['category_id']);
$stmt->execute();
$category['children_count'] = $stmt->get_result()->fetch_assoc()['count'];
}
return $categories;
}
// Get tags
function getTags($args = []) {
global $conn;
$defaults = [
'hide_empty' => true,
'orderby' => 'tag_name',
'order' => 'ASC',
'limit' => null
];
$params = array_merge($defaults, $args);
$sql = "SELECT * FROM tags";
if ($params['hide_empty']) {
$sql .= " WHERE post_count > 0";
}
$sql .= " ORDER BY {$params['orderby']} {$params['order']}";
if ($params['limit']) {
$sql .= " LIMIT " . $params['limit'];
}
$result = $conn->query($sql);
return $result->fetch_all(MYSQLI_ASSOC);
}
// Get popular posts
function getPopularPosts($limit = 5, $days = 30) {
global $conn;
$sql = "SELECT p.*, COUNT(pv.view_id) as view_count
FROM posts p
JOIN post_views pv ON p.post_id = pv.post_id
WHERE p.status = 'published'
AND pv.viewed_at >= DATE_SUB(NOW(), INTERVAL ? DAY)
GROUP BY p.post_id
ORDER BY view_count DESC
LIMIT ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("ii", $days, $limit);
$stmt->execute();
return $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
}
// Get related posts
function getRelatedPosts($post_id, $category_ids = [], $tag_ids = [], $limit = 3) {
global $conn;
$sql = "SELECT DISTINCT p.*, 
(SELECT COUNT(*) FROM post_categories WHERE post_id = p.post_id AND category_id IN (" . implode(',', $category_ids) . ")) as category_matches,
(SELECT COUNT(*) FROM post_tags WHERE post_id = p.post_id AND tag_id IN (" . implode(',', $tag_ids) . ")) as tag_matches
FROM posts p
WHERE p.post_id != ? AND p.status = 'published'
HAVING (category_matches + tag_matches) > 0
ORDER BY (category_matches + tag_matches) DESC, p.published_at DESC
LIMIT ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("ii", $post_id, $limit);
$stmt->execute();
return $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
}
// Search posts
function searchPosts($query, $page = 1, $per_page = POSTS_PER_PAGE) {
global $conn;
$offset = ($page - 1) * $per_page;
$sql = "SELECT SQL_CALC_FOUND_ROWS p.*, 
MATCH(p.title, p.content, p.excerpt) AGAINST(? IN NATURAL LANGUAGE MODE) as relevance
FROM posts p
WHERE MATCH(p.title, p.content, p.excerpt) AGAINST(? IN NATURAL LANGUAGE MODE)
AND p.status = 'published'
ORDER BY relevance DESC
LIMIT ? OFFSET ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("ssii", $query, $query, $per_page, $offset);
$stmt->execute();
$posts = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
// Get total count
$result = $conn->query("SELECT FOUND_ROWS() as total");
$total = $result->fetch_assoc()['total'];
return [
'posts' => $posts,
'total' => $total,
'pages' => ceil($total / $per_page),
'current_page' => $page
];
}
// Generate excerpt
function generateExcerpt($content, $length = 150) {
$content = strip_tags($content);
if (strlen($content) <= $length) {
return $content;
}
return substr($content, 0, strpos($content, ' ', $length)) . '...';
}
// Upload media
function uploadMedia($file, $post_id = null) {
global $conn;
$user_id = getCurrentUserId();
// Check file size
if ($file['size'] > MAX_FILE_SIZE) {
return ['success' => false, 'error' => 'File too large'];
}
// Check file type
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$allowed_types = explode(',', ALLOWED_IMAGE_TYPES . ',' . ALLOWED_DOC_TYPES);
if (!in_array($extension, $allowed_types)) {
return ['success' => false, 'error' => 'File type not allowed'];
}
// Create upload directory
$year = date('Y');
$month = date('m');
$upload_dir = UPLOAD_PATH . "media/$year/$month/";
if (!file_exists($upload_dir)) {
mkdir($upload_dir, 0755, true);
}
// Generate unique filename
$filename = uniqid() . '_' . time() . '.' . $extension;
$filepath = $upload_dir . $filename;
// Get image dimensions if it's an image
$dimensions = null;
if (in_array($extension, explode(',', ALLOWED_IMAGE_TYPES))) {
list($width, $height) = getimagesize($file['tmp_name']);
$dimensions = ['width' => $width, 'height' => $height];
// Create thumbnail
createThumbnail($file['tmp_name'], $upload_dir . 'thumb_' . $filename, 150, 150);
}
// Move uploaded file
if (move_uploaded_file($file['tmp_name'], $filepath)) {
$relative_path = "uploads/media/$year/$month/$filename";
$sql = "INSERT INTO media (user_id, post_id, file_name, file_path, file_type, 
file_size, mime_type, width, height) 
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
$stmt = $conn->prepare($sql);
$stmt->bind_param("iissssiii", 
$user_id, $post_id, $file['name'], $relative_path, $extension,
$file['size'], $file['type'], $dimensions['width'] ?? null, $dimensions['height'] ?? null
);
if ($stmt->execute()) {
$media_id = $conn->insert_id;
return [
'success' => true,
'media_id' => $media_id,
'url' => SITE_URL . $relative_path,
'thumbnail' => SITE_URL . "uploads/media/$year/$month/thumb_$filename",
'filename' => $file['name'],
'size' => $file['size']
];
}
}
return ['success' => false, 'error' => 'Failed to upload file'];
}
// Create thumbnail
function createThumbnail($source, $destination, $width, $height) {
list($original_width, $original_height) = getimagesize($source);
$ratio = max($width / $original_width, $height / $original_height);
$new_width = $original_width * $ratio;
$new_height = $original_height * $ratio;
$thumb = imagecreatetruecolor($width, $height);
// Preserve transparency for PNG
imagealphablending($thumb, false);
imagesavealpha($thumb, true);
$source_image = imagecreatefromstring(file_get_contents($source));
imagecopyresampled($thumb, $source_image, 
($width - $new_width) / 2, ($height - $new_height) / 2,
0, 0, $new_width, $new_height, $original_width, $original_height);
// Save thumbnail
imagejpeg($thumb, $destination, IMAGE_QUALITY);
imagedestroy($thumb);
imagedestroy($source_image);
return true;
}
// Send email
function sendEmail($to, $subject, $message, $from = null) {
$from = $from ?? getOption('admin_email');
$headers = "From: " . getOption('site_title') . " <$from>\r\n";
$headers .= "Reply-To: $from\r\n";
$headers .= "MIME-Version: 1.0\r\n";
$headers .= "Content-Type: text/html; charset=UTF-8\r\n";
return mail($to, $subject, $message, $headers);
}
// Notify post author about new comment
function notifyComment($post_id, $comment_id) {
$post = getPost($post_id, 'post_id');
if (!$post) return false;
$author = getUser($post['author_id']);
if (!$author) return false;
$subject = "New comment on your post: " . $post['title'];
$message = "<h3>New comment on your post</h3>";
$message .= "<p>Someone commented on your post <strong>" . $post['title'] . "</strong></p>";
$message .= "<p><a href='" . SITE_URL . "admin/comments/moderate.php?id=" . $comment_id . "'>View Comment</a></p>";
return sendEmail($author['email'], $subject, $message);
}
// Log activity
function logActivity($action, $entity_type = null, $entity_id = null, $details = []) {
global $conn;
$user_id = getCurrentUserId() ?: null;
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
$user_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
$details_json = json_encode($details);
$sql = "INSERT INTO activity_logs (user_id, action, entity_type, entity_id, details, ip_address, user_agent) 
VALUES (?, ?, ?, ?, ?, ?, ?)";
$stmt = $conn->prepare($sql);
$stmt->bind_param("ississs", $user_id, $action, $entity_type, $entity_id, $details_json, $ip, $user_agent);
return $stmt->execute();
}
// Generate pagination links
function paginate($current_page, $total_pages, $url_pattern) {
if ($total_pages <= 1) return '';
$html = '<nav class="pagination"><ul>';
// Previous button
if ($current_page > 1) {
$html .= '<li><a href="' . str_replace('{page}', $current_page - 1, $url_pattern) . '">&laquo; Previous</a></li>';
}
// Page numbers
$start = max(1, $current_page - floor(MAX_PAGE_LINKS / 2));
$end = min($total_pages, $start + MAX_PAGE_LINKS - 1);
if ($start > 1) {
$html .= '<li><a href="' . str_replace('{page}', 1, $url_pattern) . '">1</a></li>';
if ($start > 2) {
$html .= '<li class="disabled"><span>...</span></li>';
}
}
for ($i = $start; $i <= $end; $i++) {
if ($i == $current_page) {
$html .= '<li class="active"><span>' . $i . '</span></li>';
} else {
$html .= '<li><a href="' . str_replace('{page}', $i, $url_pattern) . '">' . $i . '</a></li>';
}
}
if ($end < $total_pages) {
if ($end < $total_pages - 1) {
$html .= '<li class="disabled"><span>...</span></li>';
}
$html .= '<li><a href="' . str_replace('{page}', $total_pages, $url_pattern) . '">' . $total_pages . '</a></li>';
}
// Next button
if ($current_page < $total_pages) {
$html .= '<li><a href="' . str_replace('{page}', $current_page + 1, $url_pattern) . '">Next &raquo;</a></li>';
}
$html .= '</ul></nav>';
return $html;
}
// Generate CSRF token
function generateCSRFToken() {
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
// Verify CSRF token
function verifyCSRFToken($token) {
return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
}
// Get user by ID
function getUser($user_id) {
global $conn;
$sql = "SELECT user_id, username, email, display_name, first_name, last_name, 
bio, avatar, role, website, registered_at 
FROM users WHERE user_id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $user_id);
$stmt->execute();
return $stmt->get_result()->fetch_assoc();
}
// Generate sitemap
function generateSitemap() {
global $conn;
$sitemap = '<?xml version="1.0" encoding="UTF-8"?>';
$sitemap .= '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
// Homepage
$sitemap .= '<url>';
$sitemap .= '<loc>' . SITE_URL . '</loc>';
$sitemap .= '<changefreq>daily</changefreq>';
$sitemap .= '<priority>1.0</priority>';
$sitemap .= '</url>';
// Posts
$sql = "SELECT slug, updated_at FROM posts WHERE status = 'published' ORDER BY published_at DESC";
$result = $conn->query($sql);
while ($post = $result->fetch_assoc()) {
$sitemap .= '<url>';
$sitemap .= '<loc>' . SITE_URL . 'post/' . $post['slug'] . '</loc>';
$sitemap .= '<lastmod>' . date('Y-m-d', strtotime($post['updated_at'])) . '</lastmod>';
$sitemap .= '<changefreq>monthly</changefreq>';
$sitemap .= '<priority>0.8</priority>';
$sitemap .= '</url>';
}
// Categories
$sql = "SELECT category_slug, updated_at FROM categories WHERE is_active = 1";
$result = $conn->query($sql);
while ($category = $result->fetch_assoc()) {
$sitemap .= '<url>';
$sitemap .= '<loc>' . SITE_URL . 'category/' . $category['category_slug'] . '</loc>';
$sitemap .= '<changefreq>weekly</changefreq>';
$sitemap .= '<priority>0.6</priority>';
$sitemap .= '</url>';
}
$sitemap .= '</urlset>';
file_put_contents(ROOT_PATH . 'sitemap.xml', $sitemap);
return true;
}
?>

4. index.php (Public Homepage)

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

$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$posts_per_page = getOption('posts_per_page', 10);

// Get featured posts
$featured_posts = getPosts([
'is_featured' => true,
'limit' => 3
]);

// Get latest posts
$posts_data = getPosts([
'page' => $page,
'per_page' => $posts_per_page
]);

// Get categories for sidebar
$categories = getCategories(['hide_empty' => true, 'limit' => 10]);

// Get popular posts
$popular_posts = getPopularPosts(5);
?>





<?php echo getOption('site_title'); ?>

<!-- Open Graph -->
<meta property="og:title" content="<?php echo getOption('site_title'); ?>">
<meta property="og:description" content="<?php echo getOption('site_description'); ?>">
<meta property="og:type" content="website">
<meta property="og:url" content="<?php echo SITE_URL; ?>">
<meta property="og:image" content="<?php echo SITE_URL; ?>assets/images/og-image.jpg">
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="<?php echo getOption('site_title'); ?>">
<meta name="twitter:description" content="<?php echo getOption('site_description'); ?>">
<meta name="twitter:image" content="<?php echo SITE_URL; ?>assets/images/og-image.jpg">
<!-- Styles -->
<link rel="stylesheet" href="assets/css/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- RSS Feed -->
<link rel="alternate" type="application/rss+xml" title="<?php echo getOption('site_title'); ?> RSS Feed" href="<?php echo SITE_URL; ?>api/rss-feed.php">

            <nav class="main-nav">
<ul>
<li><a href="<?php echo SITE_URL; ?>" class="active">Home</a></li>
<li><a href="<?php echo SITE_URL; ?>categories.php">Categories</a></li>
<li><a href="<?php echo SITE_URL; ?>about.php">About</a></li>
<li><a href="<?php echo SITE_URL; ?>contact.php">Contact</a></li>
<?php if (isLoggedIn()): ?>
<li class="dropdown">
<a href="#" class="dropdown-toggle">
<img src="<?php echo SITE_URL; ?>uploads/avatars/<?php echo $_SESSION['user_avatar'] ?? 'default-avatar.png'; ?>" 
alt="Avatar" class="avatar-small">
<?php echo $_SESSION['user_display_name']; ?>
</a>
<ul class="dropdown-menu">
<?php if (hasRole('admin')): ?>
<li><a href="<?php echo ADMIN_URL; ?>">Admin Dashboard</a></li>
<?php elseif (hasRole('editor')): ?>
<li><a href="<?php echo EDITOR_URL; ?>">Editor Dashboard</a></li>
<?php elseif (hasRole('author')): ?>
<li><a href="<?php echo AUTHOR_URL; ?>">Author Dashboard</a></li>
<?php endif; ?>
<li><a href="<?php echo SITE_URL; ?>profile.php">Profile</a></li>
<li><a href="<?php echo SITE_URL; ?>logout.php">Logout</a></li>
</ul>
</li>
<?php else: ?>
<li><a href="<?php echo SITE_URL; ?>login.php">Login</a></li>
<li><a href="<?php echo SITE_URL; ?>register.php" class="btn-register">Register</a></li>
<?php endif; ?>
</ul>
</nav>
</div>
</div>
</header>
<!-- Featured Posts Slider -->
<?php if (!empty($featured_posts['posts'])): ?>
<section class="featured-slider">
<div class="container">
<div class="slider">
<?php foreach ($featured_posts['posts'] as $post): ?>
<div class="slide">
<div class="slide-image">
<img src="<?php echo $post['featured_image'] ? SITE_URL . 'uploads/posts/' . $post['featured_image'] : SITE_URL . 'assets/images/default-post.jpg'; ?>" 
alt="<?php echo htmlspecialchars($post['title']); ?>">
</div>
<div class="slide-content">
<div class="slide-category">
<?php 
$post_categories = getPostCategories($post['post_id']);
if (!empty($post_categories)):

Here is a complete Online Survey System built with HTML, CSS, JavaScript, PHP, and MySQL. It allows users to create, distribute, and analyze surveys with various question types, and provides administrators with powerful tools to manage surveys and analyze responses.


📊 Project Introduction: Online Survey System

This project is a comprehensive Online Survey System designed for collecting and analyzing feedback, conducting research, and gathering opinions. It enables users to create customized surveys with multiple question types, share them with respondents, and visualize results in real-time.

The Problem it Solves:
Traditional paper-based surveys are time-consuming, expensive, and difficult to analyze. This digital platform streamlines the entire survey process - from creation to distribution to analysis - making it easy to collect and understand feedback from any audience, anywhere in the world.

Key Features:

  • Survey Creator Features:
  • Create unlimited surveys with custom titles and descriptions
  • Multiple question types: multiple choice, checkboxes, text input, rating scales, dropdowns
  • Add, edit, and reorder questions easily
  • Set required questions
  • Customize survey themes and colors
  • Generate unique survey links for distribution
  • Set survey expiration dates
  • Limit responses per user (prevent duplicate submissions)
  • Real-time response tracking
  • Respondent Features:
  • Take surveys via unique links
  • Clean, mobile-friendly interface
  • Progress indicator
  • Save and continue later (optional)
  • View confirmation after submission
  • Admin/Analytics Features:
  • Dashboard with survey statistics
  • Visual analytics (charts and graphs)
  • Export responses to CSV/Excel
  • Filter responses by date
  • View individual responses
  • Response completion rates
  • Average time to complete
  • Demographic analysis (if collected)

Technology Stack:

  • Frontend: HTML5, CSS3, JavaScript (Chart.js for analytics, Fetch API for AJAX)
  • Backend: PHP (Object-Oriented PHP with PDO)
  • Database: MySQL
  • Additional Libraries:
  • Chart.js (Data visualization)
  • PHPMailer (Email notifications)
  • FPDF (PDF export)

📁 Project File Structure

online-survey-system/
│
├── index.php                 # Homepage - Survey listings
├── take-survey.php           # Take a survey
├── survey-complete.php       # Thank you page after completion
├── results.php               # Public results (if enabled)
├── login.php                 # User login
├── register.php              # User registration
├── logout.php                # Logout script
│
├── dashboard/                 # User Dashboard (Survey Creators)
│   ├── index.php              # Dashboard home
│   ├── surveys.php            # Manage surveys
│   ├── create-survey.php      # Create new survey
│   ├── edit-survey.php        # Edit survey details
│   ├── questions.php          # Manage questions
│   ├── add-question.php       # Add question to survey
│   ├── edit-question.php      # Edit question
│   ├── responses.php          # View responses
│   ├── analytics.php          # Survey analytics
│   ├── export.php             # Export responses
│   ├── share.php              # Get survey links
│   └── settings.php           # Account settings
│
├── admin/                     # Admin Panel
│   ├── index.php              # Admin login
│   ├── dashboard.php          # Admin dashboard
│   ├── users.php              # Manage users
│   ├── surveys.php            # Manage all surveys
│   ├── categories.php         # Survey categories
│   ├── reports.php            # System reports
│   ├── settings.php           # System settings
│   └── logout.php             # Admin logout
│
├── includes/                   # Backend logic
│   ├── config.php              # Database connection
│   ├── functions.php           # Helper functions
│   ├── auth.php                # Authentication functions
│   ├── session.php             # Session management
│   └── mailer.php              # Email functions
│
├── api/                         # AJAX endpoints
│   ├── save-response.php        # Save survey response
│   ├── get-questions.php        # Get questions for survey
│   ├── check-eligibility.php    # Check if user can take survey
│   └── get-analytics.php        # Get analytics data
│
├── assets/                     # Static assets
│   ├── css/
│   │   ├── style.css           # Main styles
│   │   └── admin.css            # Admin styles
│   ├── js/
│   │   ├── main.js             # Main JavaScript
│   │   ├── survey.js           # Survey taking functionality
│   │   └── charts.js           # Chart configurations
│   ├── images/
│   │   └── logo.png            # Site logo
│   └── vendor/                  # Third-party libraries
│       ├── chart.js/            # Charts library
│       └── fpdf/                # PDF generator
│
├── uploads/                     # Uploaded files
│   └── exports/                 # Exported data
│
└── database/
└── survey_system.sql       # Database dump

🗄️ Database Setup (database/survey_system.sql)

Create a database named survey_system and run this SQL.

-- phpMyAdmin SQL Dump
-- Database: `survey_system`
CREATE DATABASE IF NOT EXISTS `survey_system`;
USE `survey_system`;
-- --------------------------------------------------------
-- 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,
`avatar` varchar(255) DEFAULT 'default-avatar.png',
`role` enum('user','admin') 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`, `role`) VALUES
('john.doe', '[email protected]', '$2y$10$YourHashedPasswordHere', 'John Doe', 'user');
-- --------------------------------------------------------
-- Table structure for table `survey_categories`
-- --------------------------------------------------------
CREATE TABLE `survey_categories` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`description` text DEFAULT NULL,
`color` varchar(20) DEFAULT '#3498db',
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `survey_categories` (`name`, `description`, `color`) VALUES
('Customer Feedback', 'Gather feedback about products and services', '#3498db'),
('Market Research', 'Understand market trends and customer preferences', '#2ecc71'),
('Employee Satisfaction', 'Measure employee engagement and satisfaction', '#e74c3c'),
('Education', 'Educational surveys and assessments', '#f1c40f'),
('Event Feedback', 'Collect feedback after events and conferences', '#9b59b6');
-- --------------------------------------------------------
-- Table structure for table `surveys`
-- --------------------------------------------------------
CREATE TABLE `surveys` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`category_id` int(11) DEFAULT NULL,
`title` varchar(200) NOT NULL,
`description` text DEFAULT NULL,
`welcome_message` text DEFAULT NULL,
`thank_you_message` text DEFAULT NULL,
`slug` varchar(200) NOT NULL,
`theme_color` varchar(20) DEFAULT '#3498db',
`logo` varchar(255) DEFAULT NULL,
`status` enum('draft','active','closed','archived') DEFAULT 'draft',
`is_public` tinyint(1) DEFAULT 1,
`requires_login` tinyint(1) DEFAULT 0,
`limit_one_response` tinyint(1) DEFAULT 1,
`allow_anonymous` tinyint(1) DEFAULT 1,
`collect_email` tinyint(1) DEFAULT 0,
`collect_name` tinyint(1) DEFAULT 0,
`show_progress_bar` tinyint(1) DEFAULT 1,
`show_question_numbers` tinyint(1) DEFAULT 1,
`start_date` datetime DEFAULT NULL,
`end_date` datetime DEFAULT NULL,
`max_responses` int(11) DEFAULT NULL,
`current_responses` int(11) DEFAULT 0,
`views` int(11) DEFAULT 0,
`completion_rate` decimal(5,2) DEFAULT 0.00,
`average_time` int(11) DEFAULT NULL COMMENT 'in seconds',
`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 `user_id` (`user_id`),
KEY `category_id` (`category_id`),
KEY `status` (`status`),
CONSTRAINT `surveys_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
CONSTRAINT `surveys_ibfk_2` FOREIGN KEY (`category_id`) REFERENCES `survey_categories` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Sample survey
INSERT INTO `surveys` (`user_id`, `category_id`, `title`, `description`, `slug`, `status`, `is_public`) VALUES
(2, 1, 'Customer Satisfaction Survey', 'Help us improve our products and services by sharing your feedback.', 'customer-satisfaction-survey', 'active', 1);
-- --------------------------------------------------------
-- Table structure for table `questions`
-- --------------------------------------------------------
CREATE TABLE `questions` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`survey_id` int(11) NOT NULL,
`type` enum('text','textarea','radio','checkbox','select','rating','scale','date','email','number','file') NOT NULL,
`question` text NOT NULL,
`description` text DEFAULT NULL,
`options` text DEFAULT NULL COMMENT 'JSON array for options',
`is_required` tinyint(1) DEFAULT 0,
`order_position` int(11) NOT NULL,
`scale_min` int(11) DEFAULT 1,
`scale_max` int(11) DEFAULT 5,
`scale_labels` text DEFAULT NULL COMMENT 'JSON for scale labels',
`validation_rules` text DEFAULT NULL COMMENT 'JSON for validation',
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
KEY `survey_id` (`survey_id`),
KEY `order_position` (`order_position`),
CONSTRAINT `questions_ibfk_1` FOREIGN KEY (`survey_id`) REFERENCES `surveys` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Sample questions for the survey
INSERT INTO `questions` (`survey_id`, `type`, `question`, `options`, `is_required`, `order_position`) VALUES
(1, 'text', 'What is your name?', NULL, 1, 1),
(1, 'email', 'What is your email address?', NULL, 1, 2),
(1, 'radio', 'How would you rate your overall satisfaction?', '["Very Satisfied","Satisfied","Neutral","Dissatisfied","Very Dissatisfied"]', 1, 3),
(1, 'scale', 'How likely are you to recommend us to others?', NULL, 1, 4),
(1, 'textarea', 'What improvements would you suggest?', NULL, 0, 5);
-- --------------------------------------------------------
-- Table structure for table `responses`
-- --------------------------------------------------------
CREATE TABLE `responses` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`survey_id` int(11) NOT NULL,
`respondent_id` varchar(100) DEFAULT NULL COMMENT 'Unique identifier for anonymous users',
`user_id` int(11) DEFAULT NULL COMMENT 'If logged in',
`respondent_name` varchar(100) DEFAULT NULL,
`respondent_email` varchar(100) DEFAULT NULL,
`ip_address` varchar(45) DEFAULT NULL,
`user_agent` text DEFAULT NULL,
`time_taken` int(11) DEFAULT NULL COMMENT 'in seconds',
`completed` tinyint(1) DEFAULT 1,
`completed_at` datetime DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
KEY `survey_id` (`survey_id`),
KEY `user_id` (`user_id`),
KEY `respondent_id` (`respondent_id`),
KEY `completed_at` (`completed_at`),
CONSTRAINT `responses_ibfk_1` FOREIGN KEY (`survey_id`) REFERENCES `surveys` (`id`) ON DELETE CASCADE,
CONSTRAINT `responses_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
-- Table structure for table `answers`
-- --------------------------------------------------------
CREATE TABLE `answers` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`response_id` int(11) NOT NULL,
`question_id` int(11) NOT NULL,
`answer` text DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
KEY `response_id` (`response_id`),
KEY `question_id` (`question_id`),
CONSTRAINT `answers_ibfk_1` FOREIGN KEY (`response_id`) REFERENCES `responses` (`id`) ON DELETE CASCADE,
CONSTRAINT `answers_ibfk_2` FOREIGN KEY (`question_id`) REFERENCES `questions` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
-- Table structure for table `survey_themes`
-- --------------------------------------------------------
CREATE TABLE `survey_themes` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`primary_color` varchar(20) NOT NULL,
`secondary_color` varchar(20) DEFAULT NULL,
`background_color` varchar(20) DEFAULT '#ffffff',
`text_color` varchar(20) DEFAULT '#333333',
`button_color` varchar(20) DEFAULT '#3498db',
`button_text_color` varchar(20) DEFAULT '#ffffff',
`is_default` tinyint(1) DEFAULT 0,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `survey_themes` (`name`, `primary_color`, `secondary_color`, `background_color`, `text_color`, `button_color`, `is_default`) VALUES
('Default Blue', '#3498db', '#2980b9', '#ffffff', '#333333', '#3498db', 1),
('Green Nature', '#27ae60', '#229954', '#f8f9fa', '#2c3e50', '#27ae60', 0),
('Purple Modern', '#9b59b6', '#8e44ad', '#ffffff', '#34495e', '#9b59b6', 0);
-- --------------------------------------------------------
-- Table structure for table `survey_logs`
-- --------------------------------------------------------
CREATE TABLE `survey_logs` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`survey_id` int(11) NOT NULL,
`action` varchar(50) NOT NULL,
`details` text DEFAULT NULL,
`ip_address` varchar(45) DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
KEY `survey_id` (`survey_id`),
CONSTRAINT `survey_logs_ibfk_1` FOREIGN KEY (`survey_id`) REFERENCES `surveys` (`id`) ON DELETE CASCADE
) 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;
-- Default settings
INSERT INTO `settings` (`setting_key`, `setting_value`) VALUES
('site_title', 'Online Survey System'),
('site_description', 'Create and analyze surveys easily'),
('site_logo', 'logo.png'),
('admin_email', '[email protected]'),
('allow_registration', '1'),
('default_theme', '1'),
('max_surveys_per_user', '50'),
('export_format', 'csv');
COMMIT;

💻 Core PHP Files

1. Database Configuration (includes/config.php)

<?php
// Database configuration
define('DB_HOST', 'localhost');
define('DB_NAME', 'survey_system');
define('DB_USER', 'root');
define('DB_PASS', '');
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'];
}
// Site configuration
define('SITE_NAME', $settings['site_title'] ?? 'Online Survey System');
define('SITE_URL', 'http://localhost/online-survey-system/');
define('ADMIN_EMAIL', $settings['admin_email'] ?? '[email protected]');
define('MAX_SURVEYS_PER_USER', $settings['max_surveys_per_user'] ?? 50);
define('UPLOAD_PATH', __DIR__ . '/../uploads/');
// 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 formatDate($date, $format = 'M d, Y') {
return date($format, strtotime($date));
}
function formatDateTime($datetime, $format = 'M d, Y g:i A') {
return date($format, strtotime($datetime));
}
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 generateRespondentId() {
return session_id() . '-' . uniqid();
}
function getQuestionTypes() {
return [
'text' => 'Short Text',
'textarea' => 'Long Text',
'radio' => 'Multiple Choice (Single Answer)',
'checkbox' => 'Multiple Choice (Multiple Answers)',
'select' => 'Dropdown',
'rating' => 'Rating (Stars)',
'scale' => 'Linear Scale',
'date' => 'Date',
'email' => 'Email',
'number' => 'Number',
'file' => 'File Upload'
];
}
function validateQuestionType($type, $answer, $question) {
switch ($type) {
case 'email':
return filter_var($answer, FILTER_VALIDATE_EMAIL) !== false;
case 'number':
return is_numeric($answer);
case 'date':
return strtotime($answer) !== false;
default:
return true;
}
}
function logSurveyAction($survey_id, $action, $details = null) {
global $pdo;
$stmt = $pdo->prepare("
INSERT INTO survey_logs (survey_id, action, details, ip_address) 
VALUES (?, ?, ?, ?)
");
$stmt->execute([$survey_id, $action, $details, $_SERVER['REMOTE_ADDR']]);
}
function sendEmail($to, $subject, $message) {
$headers = "MIME-Version: 1.0" . "\r\n";
$headers .= "Content-type:text/html;charset=UTF-8" . "\r\n";
$headers .= "From: " . SITE_NAME . " <noreply@" . $_SERVER['HTTP_HOST'] . ">\r\n";
return mail($to, $subject, $message, $headers);
}
function getSurveyStatus($survey) {
$now = new DateTime();
$start = $survey['start_date'] ? new DateTime($survey['start_date']) : null;
$end = $survey['end_date'] ? new DateTime($survey['end_date']) : null;
if ($survey['status'] != 'active') {
return ['text' => ucfirst($survey['status']), 'class' => $survey['status']];
}
if ($start && $now < $start) {
return ['text' => 'Scheduled', 'class' => 'scheduled'];
}
if ($end && $now > $end) {
return ['text' => 'Expired', 'class' => 'expired'];
}
if ($survey['max_responses'] && $survey['current_responses'] >= $survey['max_responses']) {
return ['text' => 'Full', 'class' => 'full'];
}
return ['text' => 'Active', 'class' => 'active'];
}
?>

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 = ?");
$stmt->execute([$email]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password'])) {
if ($user['status'] != 'active') {
return ['success' => false, 'message' => 'Your account is not active. Please contact support.'];
}
$_SESSION['user_id'] = $user['id'];
$_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 ['success' => true, 'role' => $user['role']];
}
return ['success' => false, 'message' => 'Invalid email or password'];
}
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);
// Insert user
$stmt = $pdo->prepare("
INSERT INTO users (username, email, password, full_name) 
VALUES (?, ?, ?, ?)
");
$success = $stmt->execute([
$data['username'],
$data['email'],
$hashedPassword,
$data['full_name']
]);
if ($success) {
// Send welcome email
$subject = "Welcome to " . SITE_NAME;
$message = "
<h1>Welcome to " . SITE_NAME . "!</h1>
<p>Dear {$data['full_name']},</p>
<p>Thank you for registering. You can now create and manage surveys.</p>
<p><a href='" . SITE_URL . "login.php'>Click here to login</a></p>
";
sendEmail($data['email'], $subject, $message);
return ['success' => true, 'message' => 'Registration successful! Please login.'];
}
return ['success' => false, 'message' => 'Registration failed. Please try again.'];
}
public static function logout() {
session_destroy();
redirect('login.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('../dashboard/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 - Survey Listings (index.php)

<?php
require_once 'includes/config.php';
// Get public surveys
$surveys = $pdo->query("
SELECT s.*, u.full_name as creator_name, c.name as category_name, c.color as category_color
FROM surveys s
JOIN users u ON s.user_id = u.id
LEFT JOIN survey_categories c ON s.category_id = c.id
WHERE s.status = 'active' AND s.is_public = 1
ORDER BY s.created_at DESC
LIMIT 12
")->fetchAll();
// Get categories
$categories = $pdo->query("SELECT * FROM survey_categories 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; ?> - Create and Share Surveys</title>
<meta name="description" content="Create professional surveys, collect responses, and analyze results with our easy-to-use survey platform.">
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
<!-- Header -->
<header class="site-header">
<div class="container">
<div class="logo">
<a href="index.php">
<h1><?php echo SITE_NAME; ?></h1>
</a>
</div>
<nav class="main-nav">
<ul>
<li><a href="index.php" class="active">Home</a></li>
<li><a href="#features">Features</a></li>
<li><a href="#pricing">Pricing</a></li>
<li><a href="#about">About</a></li>
</ul>
</nav>
<div class="auth-buttons">
<?php if (isLoggedIn()): ?>
<a href="dashboard/" class="btn btn-primary">Dashboard</a>
<a href="logout.php" class="btn">Logout</a>
<?php else: ?>
<a href="login.php" class="btn">Login</a>
<a href="register.php" class="btn btn-primary">Sign Up</a>
<?php endif; ?>
</div>
</div>
</header>
<!-- Hero Section -->
<section class="hero">
<div class="container">
<div class="hero-content">
<h1>Create Beautiful Surveys in Minutes</h1>
<p>Collect feedback, understand your audience, and make data-driven decisions with our easy-to-use survey platform.</p>
<div class="hero-actions">
<a href="register.php" class="btn btn-large btn-primary">Get Started Free</a>
<a href="#features" class="btn btn-large">Learn More</a>
</div>
<div class="hero-stats">
<div class="stat">
<span class="stat-value">10K+</span>
<span class="stat-label">Surveys Created</span>
</div>
<div class="stat">
<span class="stat-value">100K+</span>
<span class="stat-label">Responses Collected</span>
</div>
<div class="stat">
<span class="stat-value">5K+</span>
<span class="stat-label">Happy Users</span>
</div>
</div>
</div>
</div>
</section>
<!-- Features Section -->
<section id="features" class="features">
<div class="container">
<h2 class="section-title">Why Choose Our Survey Platform?</h2>
<div class="features-grid">
<div class="feature-card">
<div class="feature-icon">📝</div>
<h3>Easy Survey Creation</h3>
<p>Create surveys with our intuitive drag-and-drop builder. Choose from multiple question types and customize the look and feel.</p>
</div>
<div class="feature-card">
<div class="feature-icon">📊</div>
<h3>Real-time Analytics</h3>
<p>View responses in real-time with beautiful charts and graphs. Export data to CSV for further analysis.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🔗</div>
<h3>Easy Sharing</h3>
<p>Share your surveys via unique links, email, or embed them on your website. Control who can respond.</p>
</div>
<div class="feature-card">
<div class="feature-icon">📱</div>
<h3>Mobile Friendly</h3>
<p>Respondents can take surveys on any device - desktop, tablet, or mobile phone.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🔒</div>
<h3>Secure & Private</h3>
<p>Your data is encrypted and secure. Control access with password protection and response limits.</p>
</div>
<div class="feature-card">
<div class="feature-icon">📧</div>
<h3>Email Notifications</h3>
<p>Get notified when someone completes your survey. Send thank-you emails automatically.</p>
</div>
</div>
</div>
</section>
<!-- Categories Section -->
<section class="categories">
<div class="container">
<h2 class="section-title">Browse by Category</h2>
<div class="categories-grid">
<?php foreach ($categories as $category): ?>
<a href="surveys.php?category=<?php echo $category['id']; ?>" class="category-card" style="border-top-color: <?php echo $category['color']; ?>">
<h3><?php echo $category['name']; ?></h3>
<p><?php echo $category['description']; ?></p>
</a>
<?php endforeach; ?>
</div>
</div>
</section>
<!-- Public Surveys Section -->
<section class="public-surveys">
<div class="container">
<h2 class="section-title">Public Surveys</h2>
<div class="surveys-grid">
<?php foreach ($surveys as $survey): 
$status = getSurveyStatus($survey);
?>
<div class="survey-card">
<div class="survey-header" style="background: <?php echo $survey['category_color'] ?? '#3498db'; ?>">
<span class="survey-category"><?php echo $survey['category_name'] ?? 'Uncategorized'; ?></span>
<span class="survey-status status-<?php echo $status['class']; ?>"><?php echo $status['text']; ?></span>
</div>
<div class="survey-body">
<h3 class="survey-title"><?php echo $survey['title']; ?></h3>
<p class="survey-description"><?php echo substr($survey['description'], 0, 100); ?>...</p>
<div class="survey-meta">
<span>By <?php echo $survey['creator_name']; ?></span>
<span>📊 <?php echo number_format($survey['current_responses']); ?> responses</span>
</div>
</div>
<div class="survey-footer">
<a href="take-survey.php?slug=<?php echo $survey['slug']; ?>" class="btn btn-primary btn-block">Take Survey</a>
</div>
</div>
<?php endforeach; ?>
</div>
<?php if (count($surveys) >= 12): ?>
<div class="text-center">
<a href="surveys.php" class="btn btn-large">View All Surveys</a>
</div>
<?php endif; ?>
</div>
</section>
<!-- CTA Section -->
<section class="cta">
<div class="container">
<h2>Ready to Create Your First Survey?</h2>
<p>Join thousands of users who trust our platform for their survey needs.</p>
<a href="register.php" class="btn btn-large btn-primary">Get Started Now</a>
</div>
</section>
<!-- 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 powerful yet easy-to-use survey platform for individuals and businesses.</p>
</div>
<div class="footer-widget">
<h4>Quick Links</h4>
<ul>
<li><a href="#features">Features</a></li>
<li><a href="#pricing">Pricing</a></li>
<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>
</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">LinkedIn</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>
</body>
</html>

4. Take Survey Page (take-survey.php)

<?php
require_once 'includes/config.php';
$slug = $_GET['slug'] ?? '';
if (empty($slug)) {
redirect('index.php');
}
// Get survey details
$stmt = $pdo->prepare("
SELECT s.*, u.full_name as creator_name, t.* 
FROM surveys s
JOIN users u ON s.user_id = u.id
LEFT JOIN survey_themes t ON t.id = s.theme_id
WHERE s.slug = ?
");
$stmt->execute([$slug]);
$survey = $stmt->fetch();
if (!$survey || $survey['status'] != 'active') {
$_SESSION['error'] = 'Survey not found or not active';
redirect('index.php');
}
// Check if survey is accessible
$status = getSurveyStatus($survey);
if ($status['class'] != 'active') {
$_SESSION['error'] = 'This survey is currently ' . $status['text'];
redirect('index.php');
}
// Check if user has already responded
$can_take = true;
$error_message = '';
if ($survey['limit_one_response']) {
$respondent_id = generateRespondentId();
$check = $pdo->prepare("SELECT id FROM responses WHERE survey_id = ? AND respondent_id = ?");
$check->execute([$survey['id'], $respondent_id]);
if ($check->fetch()) {
$can_take = false;
$error_message = 'You have already taken this survey.';
}
}
if ($survey['requires_login'] && !isLoggedIn()) {
$can_take = false;
$error_message = 'You must be logged in to take this survey.';
}
// Get questions
$questions = $pdo->prepare("
SELECT * FROM questions 
WHERE survey_id = ? 
ORDER BY order_position
");
$questions->execute([$survey['id']]);
$survey_questions = $questions->fetchAll();
// Increment view count
$pdo->prepare("UPDATE surveys SET views = views + 1 WHERE id = ?")->execute([$survey['id']]);
// Start timing
$_SESSION['survey_start_time'] = time();
$_SESSION['current_survey'] = $survey['id'];
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo $survey['title']; ?> - <?php echo SITE_NAME; ?></title>
<link rel="stylesheet" href="assets/css/style.css">
<style>
:root {
--primary-color: <?php echo $survey['primary_color'] ?? '#3498db'; ?>;
--secondary-color: <?php echo $survey['secondary_color'] ?? '#2980b9'; ?>;
--background-color: <?php echo $survey['background_color'] ?? '#ffffff'; ?>;
--text-color: <?php echo $survey['text_color'] ?? '#333333'; ?>;
--button-color: <?php echo $survey['button_color'] ?? '#3498db'; ?>;
--button-text-color: <?php echo $survey['button_text_color'] ?? '#ffffff'; ?>;
}
</style>
</head>
<body style="background: var(--background-color); color: var(--text-color);">
<div class="survey-container">
<!-- Survey Header -->
<div class="survey-header" style="background: var(--primary-color);">
<div class="container">
<?php if ($survey['logo']): ?>
<img src="assets/images/<?php echo $survey['logo']; ?>" alt="Logo" class="survey-logo">
<?php endif; ?>
<h1><?php echo $survey['title']; ?></h1>
<?php if ($survey['welcome_message']): ?>
<p class="welcome-message"><?php echo $survey['welcome_message']; ?></p>
<?php endif; ?>
<?php if ($survey['show_progress_bar']): ?>
<div class="progress-container">
<div class="progress-bar" id="progress-bar" style="width: 0%"></div>
</div>
<div class="progress-text">
Question <span id="current-question">1</span> of <span id="total-questions"><?php echo count($survey_questions); ?></span>
</div>
<?php endif; ?>
</div>
</div>
<?php if (!$can_take): ?>
<div class="container">
<div class="alert alert-error"><?php echo $error_message; ?></div>
</div>
<?php else: ?>
<!-- Survey Form -->
<div class="container">
<form id="survey-form" class="survey-form" method="POST" action="api/save-response.php" enctype="multipart/form-data">
<input type="hidden" name="survey_id" value="<?php echo $survey['id']; ?>">
<!-- Respondent Info (if collecting) -->
<?php if ($survey['collect_name']): ?>
<div class="form-group">
<label for="respondent_name">Your Name <?php echo $survey['collect_name'] == 2 ? '*' : ''; ?></label>
<input type="text" id="respondent_name" name="respondent_name" 
<?php echo $survey['collect_name'] == 2 ? 'required' : ''; ?>>
</div>
<?php endif; ?>
<?php if ($survey['collect_email']): ?>
<div class="form-group">
<label for="respondent_email">Your Email <?php echo $survey['collect_email'] == 2 ? '*' : ''; ?></label>
<input type="email" id="respondent_email" name="respondent_email" 
<?php echo $survey['collect_email'] == 2 ? 'required' : ''; ?>>
</div>
<?php endif; ?>
<!-- Questions -->
<?php foreach ($survey_questions as $index => $question): 
$question_num = $index + 1;
?>
<div class="question-card" data-question="<?php echo $question_num; ?>" 
<?php echo $index > 0 ? 'style="display:none"' : ''; ?>>
<?php if ($survey['show_question_numbers']): ?>
<div class="question-number">Question <?php echo $question_num; ?></div>
<?php endif; ?>
<div class="question-text">
<?php echo $question['question']; ?>
<?php if ($question['is_required']): ?>
<span class="required">*</span>
<?php endif; ?>
</div>
<?php if ($question['description']): ?>
<div class="question-description"><?php echo $question['description']; ?></div>
<?php endif; ?>
<div class="question-input">
<?php
$options = $question['options'] ? json_decode($question['options'], true) : [];
switch ($question['type']) {
case 'text':
echo '<input type="text" name="answers[' . $question['id'] . ']" class="form-control" ' . ($question['is_required'] ? 'required' : '') . '>';
break;
case 'textarea':
echo '<textarea name="answers[' . $question['id'] . ']" rows="4" class="form-control" ' . ($question['is_required'] ? 'required' : '') . '></textarea>';
break;
case 'radio':
foreach ($options as $option) {
echo '<div class="radio-option">';
echo '<label>';
echo '<input type="radio" name="answers[' . $question['id'] . ']" value="' . htmlspecialchars($option) . '" ' . ($question['is_required'] ? 'required' : '') . '>';
echo ' ' . $option;
echo '</label>';
echo '</div>';
}
break;
case 'checkbox':
foreach ($options as $option) {
echo '<div class="checkbox-option">';
echo '<label>';
echo '<input type="checkbox" name="answers[' . $question['id'] . '][]" value="' . htmlspecialchars($option) . '">';
echo ' ' . $option;
echo '</label>';
echo '</div>';
}
break;
case 'select':
echo '<select name="answers[' . $question['id'] . ']" class="form-control" ' . ($question['is_required'] ? 'required' : '') . '>';
echo '<option value="">Select an option</option>';
foreach ($options as $option) {
echo '<option value="' . htmlspecialchars($option) . '">' . $option . '</option>';
}
echo '</select>';
break;
case 'rating':
echo '<div class="rating">';
for ($i = 1; $i <= 5; $i++) {
echo '<span class="star" data-value="' . $i . '" onclick="setRating(this, ' . $question['id'] . ')">☆</span>';
}
echo '<input type="hidden" name="answers[' . $question['id'] . ']" id="rating-' . $question['id'] . '" ' . ($question['is_required'] ? 'required' : '') . '>';
echo '</div>';
break;
case 'scale':
$min = $question['scale_min'] ?? 1;
$max = $question['scale_max'] ?? 5;
$labels = $question['scale_labels'] ? json_decode($question['scale_labels'], true) : [];
echo '<div class="scale">';
for ($i = $min; $i <= $max; $i++) {
echo '<div class="scale-option">';
echo '<input type="radio" name="answers[' . $question['id'] . ']" value="' . $i . '" id="scale-' . $question['id'] . '-' . $i . '" ' . ($question['is_required'] ? 'required' : '') . '>';
echo '<label for="scale-' . $question['id'] . '-' . $i . '">' . $i . '</label>';
if (isset($labels[$i])) {
echo '<span class="scale-label">' . $labels[$i] . '</span>';
}
echo '</div>';
}
echo '</div>';
break;
case 'date':
echo '<input type="date" name="answers[' . $question['id'] . ']" class="form-control" ' . ($question['is_required'] ? 'required' : '') . '>';
break;
case 'email':
echo '<input type="email" name="answers[' . $question['id'] . ']" class="form-control" ' . ($question['is_required'] ? 'required' : '') . '>';
break;
case 'number':
echo '<input type="number" name="answers[' . $question['id'] . ']" class="form-control" ' . ($question['is_required'] ? 'required' : '') . '>';
break;
case 'file':
echo '<input type="file" name="answers[' . $question['id'] . ']" class="form-control" ' . ($question['is_required'] ? 'required' : '') . '>';
break;
}
?>
</div>
<div class="question-navigation">
<?php if ($index > 0): ?>
<button type="button" class="btn" onclick="previousQuestion()">Previous</button>
<?php endif; ?>
<?php if ($index < count($survey_questions) - 1): ?>
<button type="button" class="btn btn-primary" onclick="nextQuestion()">Next</button>
<?php else: ?>
<button type="submit" class="btn btn-success">Submit Survey</button>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</form>
</div>
<?php endif; ?>
</div>
<script src="assets/js/survey.js"></script>
<script>
let currentQuestion = 1;
const totalQuestions = <?php echo count($survey_questions); ?>;
function nextQuestion() {
if (currentQuestion < totalQuestions) {
document.querySelector(`[data-question="${currentQuestion}"]`).style.display = 'none';
currentQuestion++;
document.querySelector(`[data-question="${currentQuestion}"]`).style.display = 'block';
updateProgress();
}
}
function previousQuestion() {
if (currentQuestion > 1) {
document.querySelector(`[data-question="${currentQuestion}"]`).style.display = 'none';
currentQuestion--;
document.querySelector(`[data-question="${currentQuestion}"]`).style.display = 'block';
updateProgress();
}
}
function updateProgress() {
const progress = (currentQuestion / totalQuestions) * 100;
document.getElementById('progress-bar').style.width = progress + '%';
document.getElementById('current-question').textContent = currentQuestion;
}
function setRating(star, questionId) {
const rating = star.dataset.value;
const stars = star.parentElement.querySelectorAll('.star');
stars.forEach((s, index) => {
if (index < rating) {
s.textContent = '★';
} else {
s.textContent = '☆';
}
});
document.getElementById('rating-' + questionId).value = rating;
}
</script>
</body>
</html>

5. API - Save Response (api/save-response.php)

<?php
require_once '../includes/config.php';
header('Content-Type: application/json');
$survey_id = (int)($_POST['survey_id'] ?? 0);
if (!$survey_id) {
echo json_encode(['success' => false, 'message' => 'Invalid survey']);
exit;
}
// Verify survey exists and is active
$stmt = $pdo->prepare("SELECT * FROM surveys WHERE id = ?");
$stmt->execute([$survey_id]);
$survey = $stmt->fetch();
if (!$survey || $survey['status'] != 'active') {
echo json_encode(['success' => false, 'message' => 'Survey not found or inactive']);
exit;
}
// Check if user has already responded
if ($survey['limit_one_response']) {
$respondent_id = generateRespondentId();
$check = $pdo->prepare("SELECT id FROM responses WHERE survey_id = ? AND respondent_id = ?");
$check->execute([$survey_id, $respondent_id]);
if ($check->fetch()) {
echo json_encode(['success' => false, 'message' => 'You have already taken this survey']);
exit;
}
}
try {
$pdo->beginTransaction();
// Calculate time taken
$time_taken = null;
if (isset($_SESSION['survey_start_time']) && $_SESSION['current_survey'] == $survey_id) {
$time_taken = time() - $_SESSION['survey_start_time'];
}
// Create response record
$stmt = $pdo->prepare("
INSERT INTO responses (
survey_id, respondent_id, user_id, respondent_name, respondent_email,
ip_address, user_agent, time_taken, completed_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW())
");
$respondent_id = generateRespondentId();
$user_id = isLoggedIn() ? $_SESSION['user_id'] : null;
$respondent_name = $_POST['respondent_name'] ?? null;
$respondent_email = $_POST['respondent_email'] ?? null;
$ip_address = $_SERVER['REMOTE_ADDR'];
$user_agent = $_SERVER['HTTP_USER_AGENT'];
$stmt->execute([
$survey_id,
$respondent_id,
$user_id,
$respondent_name,
$respondent_email,
$ip_address,
$user_agent,
$time_taken
]);
$response_id = $pdo->lastInsertId();
// Save answers
$answers = $_POST['answers'] ?? [];
$answer_stmt = $pdo->prepare("
INSERT INTO answers (response_id, question_id, answer) VALUES (?, ?, ?)
");
foreach ($answers as $question_id => $answer) {
if (is_array($answer)) {
$answer = json_encode($answer);
}
$answer_stmt->execute([$response_id, $question_id, $answer]);
}
// Update survey response count
$pdo->prepare("
UPDATE surveys 
SET current_responses = current_responses + 1 
WHERE id = ?
")->execute([$survey_id]);
// Update average time
if ($time_taken) {
$pdo->prepare("
UPDATE surveys 
SET average_time = (
SELECT AVG(time_taken) FROM responses WHERE survey_id = ?
) WHERE id = ?
")->execute([$survey_id, $survey_id]);
}
// Log action
logSurveyAction($survey_id, 'response', 'New response submitted');
$pdo->commit();
// Clear session
unset($_SESSION['survey_start_time']);
unset($_SESSION['current_survey']);
// Send confirmation email if requested
if ($respondent_email && $survey['thank_you_message']) {
$subject = "Thank you for completing the survey";
$message = "
<h1>Thank You!</h1>
<p>Dear " . ($respondent_name ?: 'Respondent') . ",</p>
<p>" . nl2br($survey['thank_you_message']) . "</p>
<p>Best regards,<br>" . SITE_NAME . " Team</p>
";
sendEmail($respondent_email, $subject, $message);
}
echo json_encode([
'success' => true,
'message' => 'Survey submitted successfully',
'redirect' => SITE_URL . 'survey-complete.php?id=' . $survey_id
]);
} catch (Exception $e) {
$pdo->rollBack();
echo json_encode(['success' => false, 'message' => 'Error saving response: ' . $e->getMessage()]);
}
?>

6. Dashboard - Manage Surveys (dashboard/surveys.php)

<?php
require_once '../includes/config.php';
require_once '../includes/auth.php';
Auth::checkLogin();
$user_id = $_SESSION['user_id'];
// Handle survey deletion
if (isset($_GET['delete'])) {
$id = (int)$_GET['delete'];
// Verify survey belongs to user
$check = $pdo->prepare("SELECT id FROM surveys WHERE id = ? AND user_id = ?");
$check->execute([$id, $user_id]);
if ($check->fetch()) {
$pdo->prepare("DELETE FROM surveys WHERE id = ?")->execute([$id]);
$_SESSION['success'] = 'Survey deleted successfully';
}
redirect('surveys.php');
}
// Get user's surveys
$surveys = $pdo->prepare("
SELECT s.*, 
(SELECT COUNT(*) FROM responses WHERE survey_id = s.id) as response_count,
c.name as category_name
FROM surveys s
LEFT JOIN survey_categories c ON s.category_id = c.id
WHERE s.user_id = ?
ORDER BY s.created_at DESC
");
$surveys->execute([$user_id]);
$user_surveys = $surveys->fetchAll();
// Get response statistics
$total_responses = 0;
$active_surveys = 0;
foreach ($user_surveys as $survey) {
$total_responses += $survey['response_count'];
if ($survey['status'] == 'active') {
$active_surveys++;
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Surveys - <?php echo SITE_NAME; ?></title>
<link rel="stylesheet" href="../assets/css/admin.css">
</head>
<body>
<div class="dashboard-wrapper">
<!-- Sidebar -->
<aside class="sidebar">
<div class="sidebar-header">
<h2>Survey Dashboard</h2>
<p>Welcome, <?php echo $_SESSION['user_name']; ?></p>
</div>
<nav class="sidebar-nav">
<ul>
<li><a href="index.php">Dashboard</a></li>
<li><a href="surveys.php" class="active">My Surveys</a></li>
<li><a href="create-survey.php">Create Survey</a></li>
<li><a href="responses.php">Responses</a></li>
<li><a href="analytics.php">Analytics</a></li>
<li><a href="settings.php">Account Settings</a></li>
<li><a href="../logout.php">Logout</a></li>
</ul>
</nav>
</aside>
<!-- Main Content -->
<main class="dashboard-main">
<div class="container">
<div class="page-header">
<h1>My Surveys</h1>
<a href="create-survey.php" class="btn btn-primary">+ Create New Survey</a>
</div>
<!-- Stats Cards -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon">📋</div>
<div class="stat-details">
<h3><?php echo count($user_surveys); ?></h3>
<p>Total Surveys</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">✅</div>
<div class="stat-details">
<h3><?php echo $active_surveys; ?></h3>
<p>Active Surveys</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">📊</div>
<div class="stat-details">
<h3><?php echo number_format($total_responses); ?></h3>
<p>Total Responses</p>
</div>
</div>
</div>
<?php if (isset($_SESSION['success'])): ?>
<div class="alert alert-success"><?php echo $_SESSION['success']; unset($_SESSION['success']); ?></div>
<?php endif; ?>
<!-- Surveys Table -->
<div class="card">
<div class="card-header">
<h3>Your Surveys</h3>
</div>
<div class="card-body">
<?php if (empty($user_surveys)): ?>
<p class="text-center">You haven't created any surveys yet.</p>
<div class="text-center">
<a href="create-survey.php" class="btn btn-primary">Create Your First Survey</a>
</div>
<?php else: ?>
<table class="data-table">
<thead>
<tr>
<th>Title</th>
<th>Category</th>
<th>Responses</th>
<th>Status</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($user_surveys as $survey): 
$status = getSurveyStatus($survey);
?>
<tr>
<td>
<strong><?php echo $survey['title']; ?></strong>
<?php if ($survey['is_public']): ?>
<span class="badge badge-success">Public</span>
<?php endif; ?>
</td>
<td><?php echo $survey['category_name'] ?? 'Uncategorized'; ?></td>
<td><?php echo number_format($survey['response_count']); ?></td>
<td>
<span class="badge badge-<?php echo $status['class']; ?>">
<?php echo $status['text']; ?>
</span>
</td>
<td><?php echo formatDate($survey['created_at']); ?></td>
<td class="actions">
<a href="edit-survey.php?id=<?php echo $survey['id']; ?>" class="btn-small">Edit</a>
<a href="questions.php?survey=<?php echo $survey['id']; ?>" class="btn-small">Questions</a>
<a href="responses.php?survey=<?php echo $survey['id']; ?>" class="btn-small">Responses</a>
<a href="analytics.php?survey=<?php echo $survey['id']; ?>" class="btn-small">Analytics</a>
<a href="share.php?id=<?php echo $survey['id']; ?>" class="btn-small">Share</a>
<a href="?delete=<?php echo $survey['id']; ?>" 
class="btn-small btn-danger"
onclick="return confirm('Are you sure you want to delete this survey? All responses will be lost.')">Delete</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
</div>
</div>
</main>
</div>
</body>
</html>

7. Dashboard - Create Survey (dashboard/create-survey.php)

<?php
require_once '../includes/config.php';
require_once '../includes/auth.php';
Auth::checkLogin();
$user_id = $_SESSION['user_id'];
// Check survey limit
$count = $pdo->prepare("SELECT COUNT(*) FROM surveys WHERE user_id = ?");
$count->execute([$user_id]);
$survey_count = $count->fetchColumn();
if ($survey_count >= MAX_SURVEYS_PER_USER && !isAdmin()) {
$_SESSION['error'] = 'You have reached the maximum number of surveys. Please upgrade your account.';
redirect('surveys.php');
}
// Get categories
$categories = $pdo->query("SELECT * FROM survey_categories ORDER BY name")->fetchAll();
// Get themes
$themes = $pdo->query("SELECT * FROM survey_themes ORDER BY is_default DESC, name")->fetchAll();
$errors = [];
$form_data = [
'title' => '',
'description' => '',
'welcome_message' => '',
'thank_you_message' => '',
'category_id' => '',
'theme_id' => '',
'is_public' => 1,
'requires_login' => 0,
'limit_one_response' => 1,
'allow_anonymous' => 1,
'collect_name' => 0,
'collect_email' => 0,
'show_progress_bar' => 1,
'show_question_numbers' => 1,
'start_date' => '',
'end_date' => '',
'max_responses' => ''
];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Sanitize input
$form_data['title'] = sanitize($_POST['title']);
$form_data['description'] = sanitize($_POST['description']);
$form_data['welcome_message'] = sanitize($_POST['welcome_message']);
$form_data['thank_you_message'] = sanitize($_POST['thank_you_message']);
$form_data['category_id'] = !empty($_POST['category_id']) ? (int)$_POST['category_id'] : null;
$form_data['theme_id'] = !empty($_POST['theme_id']) ? (int)$_POST['theme_id'] : null;
$form_data['is_public'] = isset($_POST['is_public']) ? 1 : 0;
$form_data['requires_login'] = isset($_POST['requires_login']) ? 1 : 0;
$form_data['limit_one_response'] = isset($_POST['limit_one_response']) ? 1 : 0;
$form_data['allow_anonymous'] = isset($_POST['allow_anonymous']) ? 1 : 0;
$form_data['collect_name'] = (int)($_POST['collect_name'] ?? 0);
$form_data['collect_email'] = (int)($_POST['collect_email'] ?? 0);
$form_data['show_progress_bar'] = isset($_POST['show_progress_bar']) ? 1 : 0;
$form_data['show_question_numbers'] = isset($_POST['show_question_numbers']) ? 1 : 0;
$form_data['start_date'] = !empty($_POST['start_date']) ? $_POST['start_date'] : null;
$form_data['end_date'] = !empty($_POST['end_date']) ? $_POST['end_date'] : null;
$form_data['max_responses'] = !empty($_POST['max_responses']) ? (int)$_POST['max_responses'] : null;
// Validate
if (empty($form_data['title'])) {
$errors['title'] = 'Title is required';
}
if (empty($errors)) {
try {
// Create slug
$slug = createSlug($form_data['title']);
// Check if slug exists
$check = $pdo->prepare("SELECT id FROM surveys WHERE slug = ?");
$check->execute([$slug]);
if ($check->fetch()) {
$slug .= '-' . uniqid();
}
// Insert survey
$stmt = $pdo->prepare("
INSERT INTO surveys (
user_id, title, description, welcome_message, thank_you_message,
slug, category_id, theme_id, is_public, requires_login,
limit_one_response, allow_anonymous, collect_name, collect_email,
show_progress_bar, show_question_numbers, start_date, end_date,
max_responses, status
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'draft')
");
$stmt->execute([
$user_id,
$form_data['title'],
$form_data['description'],
$form_data['welcome_message'],
$form_data['thank_you_message'],
$slug,
$form_data['category_id'],
$form_data['theme_id'],
$form_data['is_public'],
$form_data['requires_login'],
$form_data['limit_one_response'],
$form_data['allow_anonymous'],
$form_data['collect_name'],
$form_data['collect_email'],
$form_data['show_progress_bar'],
$form_data['show_question_numbers'],
$form_data['start_date'],
$form_data['end_date'],
$form_data['max_responses']
]);
$survey_id = $pdo->lastInsertId();
$_SESSION['success'] = 'Survey created successfully! Now add your questions.';
redirect('questions.php?survey=' . $survey_id);
} catch (Exception $e) {
$errors['general'] = 'Failed to create survey: ' . $e->getMessage();
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Create Survey - <?php echo SITE_NAME; ?></title>
<link rel="stylesheet" href="../assets/css/admin.css">
</head>
<body>
<div class="dashboard-wrapper">
<!-- Sidebar -->
<aside class="sidebar">
<div class="sidebar-header">
<h2>Survey Dashboard</h2>
<p>Welcome, <?php echo $_SESSION['user_name']; ?></p>
</div>
<nav class="sidebar-nav">
<ul>
<li><a href="index.php">Dashboard</a></li>
<li><a href="surveys.php">My Surveys</a></li>
<li><a href="create-survey.php" class="active">Create Survey</a></li>
<li><a href="responses.php">Responses</a></li>
<li><a href="analytics.php">Analytics</a></li>
<li><a href="settings.php">Account Settings</a></li>
<li><a href="../logout.php">Logout</a></li>
</ul>
</nav>
</aside>
<!-- Main Content -->
<main class="dashboard-main">
<div class="container">
<div class="page-header">
<h1>Create New Survey</h1>
<a href="surveys.php" class="btn">← Back to Surveys</a>
</div>
<?php if (!empty($errors['general'])): ?>
<div class="alert alert-error"><?php echo $errors['general']; ?></div>
<?php endif; ?>
<div class="card">
<div class="card-header">
<h3>Survey Details</h3>
</div>
<div class="card-body">
<form method="POST" action="" class="admin-form">
<!-- Basic Information -->
<h4>Basic Information</h4>
<div class="form-group">
<label for="title">Survey Title *</label>
<input type="text" id="title" name="title" 
value="<?php echo htmlspecialchars($form_data['title']); ?>" required>
<?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="3"><?php echo $form_data['description']; ?></textarea>
<small>Brief description of your survey (optional)</small>
</div>
<div class="form-group">
<label for="welcome_message">Welcome Message</label>
<textarea id="welcome_message" name="welcome_message" rows="3"><?php echo $form_data['welcome_message']; ?></textarea>
<small>Message shown to respondents before they start (optional)</small>
</div>
<div class="form-group">
<label for="thank_you_message">Thank You Message</label>
<textarea id="thank_you_message" name="thank_you_message" rows="3"><?php echo $form_data['thank_you_message']; ?></textarea>
<small>Message shown after completion (optional)</small>
</div>
<!-- Settings -->
<h4>Survey Settings</h4>
<div class="form-row">
<div class="form-group col-md-6">
<label for="category_id">Category</label>
<select id="category_id" name="category_id">
<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>
</div>
<div class="form-group col-md-6">
<label for="theme_id">Theme</label>
<select id="theme_id" name="theme_id">
<option value="">Default Theme</option>
<?php foreach ($themes as $theme): ?>
<option value="<?php echo $theme['id']; ?>" 
<?php echo $form_data['theme_id'] == $theme['id'] ? 'selected' : ''; ?>>
<?php echo $theme['name']; ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group col-md-4">
<label for="start_date">Start Date</label>
<input type="datetime-local" id="start_date" name="start_date" 
value="<?php echo $form_data['start_date']; ?>">
</div>
<div class="form-group col-md-4">
<label for="end_date">End Date</label>
<input type="datetime-local" id="end_date" name="end_date" 
value="<?php echo $form_data['end_date']; ?>">
</div>
<div class="form-group col-md-4">
<label for="max_responses">Max Responses</label>
<input type="number" id="max_responses" name="max_responses" 
value="<?php echo $form_data['max_responses']; ?>" min="1">
<small>Leave empty for unlimited</small>
</div>
</div>
<!-- Options -->
<h4>Survey Options</h4>
<div class="checkbox-group">
<label>
<input type="checkbox" name="is_public" value="1" 
<?php echo $form_data['is_public'] ? 'checked' : ''; ?>>
Make survey public (visible in directory)
</label>
</div>
<div class="checkbox-group">
<label>
<input type="checkbox" name="requires_login" value="1" 
<?php echo $form_data['requires_login'] ? 'checked' : ''; ?>>
Require login to take survey
</label>
</div>
<div class="checkbox-group">
<label>
<input type="checkbox" name="limit_one_response" value="1" 
<?php echo $form_data['limit_one_response'] ? 'checked' : ''; ?>>
Limit one response per person
</label>
</div>
<div class="checkbox-group">
<label>
<input type="checkbox" name="show_progress_bar" value="1" 
<?php echo $form_data['show_progress_bar'] ? 'checked' : ''; ?>>
Show progress bar
</label>
</div>
<div class="checkbox-group">
<label>
<input type="checkbox" name="show_question_numbers" value="1" 
<?php echo $form_data['show_question_numbers'] ? 'checked' : ''; ?>>
Show question numbers
</label>
</div>
<!-- Respondent Information -->
<h4>Respondent Information</h4>
<div class="form-group">
<label for="collect_name">Collect Respondent Name</label>
<select id="collect_name" name="collect_name">
<option value="0" <?php echo $form_data['collect_name'] == 0 ? 'selected' : ''; ?>>Don't collect</option>
<option value="1" <?php echo $form_data['collect_name'] == 1 ? 'selected' : ''; ?>>Optional</option>
<option value="2" <?php echo $form_data['collect_name'] == 2 ? 'selected' : ''; ?>>Required</option>
</select>
</div>
<div class="form-group">
<label for="collect_email">Collect Respondent Email</label>
<select id="collect_email" name="collect_email">
<option value="0" <?php echo $form_data['collect_email'] == 0 ? 'selected' : ''; ?>>Don't collect</option>
<option value="1" <?php echo $form_data['collect_email'] == 1 ? 'selected' : ''; ?>>Optional</option>
<option value="2" <?php echo $form_data['collect_email'] == 2 ? 'selected' : ''; ?>>Required</option>
</select>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">Create Survey & Add Questions</button>
<button type="reset" class="btn">Reset</button>
</div>
</form>
</div>
</div>
</div>
</main>
</div>
</body>
</html>

8. Dashboard - Add Questions (dashboard/add-question.php)

<?php
require_once '../includes/config.php';
require_once '../includes/auth.php';
Auth::checkLogin();
$survey_id = isset($_GET['survey']) ? (int)$_GET['survey'] : 0;
if (!$survey_id) {
redirect('surveys.php');
}
// Verify survey belongs to user
$check = $pdo->prepare("SELECT * FROM surveys WHERE id = ? AND user_id = ?");
$check->execute([$survey_id, $_SESSION['user_id']]);
$survey = $check->fetch();
if (!$survey) {
$_SESSION['error'] = 'Survey not found';
redirect('surveys.php');
}
$question_types = getQuestionTypes();
$errors = [];
$form_data = [
'type' => 'text',
'question' => '',
'description' => '',
'options' => '',
'is_required' => 1,
'scale_min' => 1,
'scale_max' => 5,
'scale_labels' => ''
];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Get next order position
$pos = $pdo->prepare("SELECT MAX(order_position) FROM questions WHERE survey_id = ?");
$pos->execute([$survey_id]);
$next_position = ($pos->fetchColumn() ?? 0) + 1;
// Sanitize input
$form_data['type'] = $_POST['type'];
$form_data['question'] = sanitize($_POST['question']);
$form_data['description'] = sanitize($_POST['description']);
$form_data['is_required'] = isset($_POST['is_required']) ? 1 : 0;
$form_data['scale_min'] = (int)($_POST['scale_min'] ?? 1);
$form_data['scale_max'] = (int)($_POST['scale_max'] ?? 5);
// Handle options for multiple choice questions
if (in_array($form_data['type'], ['radio', 'checkbox', 'select'])) {
$options = explode("\n", trim($_POST['options']));
$options = array_map('trim', $options);
$options = array_filter($options);
$form_data['options'] = json_encode(array_values($options));
}
// Handle scale labels
if ($form_data['type'] == 'scale' && !empty($_POST['scale_labels'])) {
$labels = [];
$lines = explode("\n", trim($_POST['scale_labels']));
foreach ($lines as $line) {
if (strpos($line, '=') !== false) {
list($value, $label) = explode('=', $line, 2);
$labels[trim($value)] = trim($label);
}
}
$form_data['scale_labels'] = json_encode($labels);
}
// Validate
if (empty($form_data['question'])) {
$errors['question'] = 'Question is required';
}
if (in_array($form_data['type'], ['radio', 'checkbox', 'select']) && empty($_POST['options'])) {
$errors['options'] = 'Options are required for this question type';
}
if (empty($errors)) {
// Insert question
$stmt = $pdo->prepare("
INSERT INTO questions (
survey_id, type, question, description, options, is_required,
order_position, scale_min, scale_max, scale_labels
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
");
$stmt->execute([
$survey_id,
$form_data['type'],
$form_data['question'],
$form_data['description'],
$form_data['options'] ?? null,
$form_data['is_required'],
$next_position,
$form_data['scale_min'],
$form_data['scale_max'],
$form_data['scale_labels'] ?? null
]);
// Update survey's last updated timestamp
$pdo->prepare("UPDATE surveys SET updated_at = NOW() WHERE id = ?")->execute([$survey_id]);
$_SESSION['success'] = 'Question added successfully';
if (isset($_POST['save_and_add'])) {
redirect('add-question.php?survey=' . $survey_id);
} else {
redirect('questions.php?survey=' . $survey_id);
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Add Question - <?php echo $survey['title']; ?></title>
<link rel="stylesheet" href="../assets/css/admin.css">
</head>
<body>
<div class="dashboard-wrapper">
<!-- Sidebar -->
<aside class="sidebar">
<div class="sidebar-header">
<h2>Survey Dashboard</h2>
<p>Welcome, <?php echo $_SESSION['user_name']; ?></p>
</div>
<nav class="sidebar-nav">
<ul>
<li><a href="index.php">Dashboard</a></li>
<li><a href="surveys.php">My Surveys</a></li>
<li><a href="create-survey.php">Create Survey</a></li>
<li><a href="responses.php">Responses</a></li>
<li><a href="analytics.php">Analytics</a></li>
<li><a href="settings.php">Account Settings</a></li>
<li><a href="../logout.php">Logout</a></li>
</ul>
</nav>
</aside>
<!-- Main Content -->
<main class="dashboard-main">
<div class="container">
<div class="page-header">
<h1>Add Question to: <?php echo $survey['title']; ?></h1>
<div>
<a href="questions.php?survey=<?php echo $survey_id; ?>" class="btn">← Back to Questions</a>
</div>
</div>
<?php if (isset($_SESSION['success'])): ?>
<div class="alert alert-success"><?php echo $_SESSION['success']; unset($_SESSION['success']); ?></div>
<?php endif; ?>
<div class="card">
<div class="card-header">
<h3>Question Details</h3>
</div>
<div class="card-body">
<form method="POST" action="" class="admin-form">
<div class="form-group">
<label for="type">Question Type</label>
<select id="type" name="type" onchange="toggleQuestionType()">
<?php foreach ($question_types as $value => $label): ?>
<option value="<?php echo $value; ?>" 
<?php echo $form_data['type'] == $value ? 'selected' : ''; ?>>
<?php echo $label; ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label for="question">Question *</label>
<input type="text" id="question" name="question" 
value="<?php echo htmlspecialchars($form_data['question']); ?>" required>
<?php if (isset($errors['question'])): ?>
<span class="error"><?php echo $errors['question']; ?></span>
<?php endif; ?>
</div>
<div class="form-group">
<label for="description">Description (Optional)</label>
<textarea id="description" name="description" rows="2"><?php echo $form_data['description']; ?></textarea>
<small>Additional instructions or context for the question</small>
</div>
<!-- Options for multiple choice questions -->
<div id="options-field" style="display: none;">
<div class="form-group">
<label for="options">Options (One per line)</label>
<textarea id="options" name="options" rows="5"><?php echo $form_data['options']; ?></textarea>
<?php if (isset($errors['options'])): ?>
<span class="error"><?php echo $errors['options']; ?></span>
<?php endif; ?>
</div>
</div>
<!-- Scale settings -->
<div id="scale-field" style="display: none;">
<div class="form-row">
<div class="form-group col-md-6">
<label for="scale_min">Minimum Value</label>
<input type="number" id="scale_min" name="scale_min" 
value="<?php echo $form_data['scale_min']; ?>" min="0" max="10">
</div>
<div class="form-group col-md-6">
<label for="scale_max">Maximum Value</label>
<input type="number" id="scale_max" name="scale_max" 
value="<?php echo $form_data['scale_max']; ?>" min="1" max="10">
</div>
</div>
<div class="form-group">
<label for="scale_labels">Scale Labels (Optional)</label>
<textarea id="scale_labels" name="scale_labels" rows="3" 
placeholder="1=Very Poor&#10;2=Poor&#10;3=Average&#10;4=Good&#10;5=Excellent"><?php echo $form_data['scale_labels']; ?></textarea>
<small>Format: value=label (one per line)</small>
</div>
</div>
<div class="checkbox-group">
<label>
<input type="checkbox" name="is_required" value="1" 
<?php echo $form_data['is_required'] ? 'checked' : ''; ?>>
This question is required
</label>
</div>
<div class="form-actions">
<button type="submit" name="save" class="btn btn-primary">Save Question</button>
<button type="submit" name="save_and_add" class="btn btn-success">Save & Add Another</button>
<a href="questions.php?survey=<?php echo $survey_id; ?>" class="btn">Cancel</a>
</div>
</form>
</div>
</div>
</div>
</main>
</div>
<script>
function toggleQuestionType() {
const type = document.getElementById('type').value;
const optionsField = document.getElementById('options-field');
const scaleField = document.getElementById('scale-field');
optionsField.style.display = 'none';
scaleField.style.display = 'none';
if (['radio', 'checkbox', 'select'].includes(type)) {
optionsField.style.display = 'block';
} else if (type === 'scale') {
scaleField.style.display = 'block';
}
}
// Initialize on page load
toggleQuestionType();
</script>
</body>
</html>

9. Dashboard - Analytics (dashboard/analytics.php)

<?php
require_once '../includes/config.php';
require_once '../includes/auth.php';
Auth::checkLogin();
$survey_id = isset($_GET['survey']) ? (int)$_GET['survey'] : 0;
if (!$survey_id) {
redirect('surveys.php');
}
// Verify survey belongs to user
$check = $pdo->prepare("SELECT * FROM surveys WHERE id = ? AND user_id = ?");
$check->execute([$survey_id, $_SESSION['user_id']]);
$survey = $check->fetch();
if (!$survey && !isAdmin()) {
redirect('surveys.php');
}
// Get response statistics
$total_responses = $pdo->prepare("SELECT COUNT(*) FROM responses WHERE survey_id = ?");
$total_responses->execute([$survey_id]);
$response_count = $total_responses->fetchColumn();
$completion_rate = $pdo->prepare("
SELECT 
COUNT(*) as total,
SUM(CASE WHEN completed = 1 THEN 1 ELSE 0 END) as completed
FROM responses WHERE survey_id = ?
");
$completion_rate->execute([$survey_id]);
$completion = $completion_rate->fetch();
$avg_time = $pdo->prepare("SELECT AVG(time_taken) FROM responses WHERE survey_id = ? AND time_taken IS NOT NULL");
$avg_time->execute([$survey_id]);
$average_time = $avg_time->fetchColumn();
// Get responses over time
$responses_over_time = $pdo->prepare("
SELECT DATE(completed_at) as date, COUNT(*) as count
FROM responses
WHERE survey_id = ? AND completed_at IS NOT NULL
GROUP BY DATE(completed_at)
ORDER BY date
");
$responses_over_time->execute([$survey_id]);
$time_data = $responses_over_time->fetchAll();
// Get questions and answers for analysis
$questions = $pdo->prepare("
SELECT q.*, 
(SELECT COUNT(*) FROM answers WHERE question_id = q.id) as answer_count
FROM questions q
WHERE q.survey_id = ?
ORDER BY q.order_position
");
$questions->execute([$survey_id]);
$survey_questions = $questions->fetchAll();
// Get answers for each question
$answers_data = [];
foreach ($survey_questions as $question) {
$answers = $pdo->prepare("
SELECT a.answer, COUNT(*) as count
FROM answers a
WHERE a.question_id = ?
GROUP BY a.answer
ORDER BY count DESC
");
$answers->execute([$question['id']]);
$answers_data[$question['id']] = $answers->fetchAll();
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Analytics - <?php echo $survey['title']; ?></title>
<link rel="stylesheet" href="../assets/css/admin.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
<div class="dashboard-wrapper">
<!-- Sidebar -->
<aside class="sidebar">
<div class="sidebar-header">
<h2>Survey Dashboard</h2>
<p>Welcome, <?php echo $_SESSION['user_name']; ?></p>
</div>
<nav class="sidebar-nav">
<ul>
<li><a href="index.php">Dashboard</a></li>
<li><a href="surveys.php">My Surveys</a></li>
<li><a href="create-survey.php">Create Survey</a></li>
<li><a href="responses.php?survey=<?php echo $survey_id; ?>">Responses</a></li>
<li><a href="analytics.php?survey=<?php echo $survey_id; ?>" class="active">Analytics</a></li>
<li><a href="export.php?survey=<?php echo $survey_id; ?>">Export</a></li>
<li><a href="settings.php">Account Settings</a></li>
<li><a href="../logout.php">Logout</a></li>
</ul>
</nav>
</aside>
<!-- Main Content -->
<main class="dashboard-main">
<div class="container">
<div class="page-header">
<h1>Analytics: <?php echo $survey['title']; ?></h1>
<a href="surveys.php" class="btn">← Back to Surveys</a>
</div>
<!-- Summary Stats -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon">📊</div>
<div class="stat-details">
<h3><?php echo number_format($response_count); ?></h3>
<p>Total Responses</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">✅</div>
<div class="stat-details">
<h3><?php echo $completion['completed'] ?? 0; ?></h3>
<p>Completed</p>
<small>
<?php 
$rate = $response_count > 0 ? round(($completion['completed'] / $response_count) * 100, 1) : 0;
echo $rate . '% completion rate';
?>
</small>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">⏱️</div>
<div class="stat-details">
<h3>
<?php 
if ($average_time) {
$minutes = floor($average_time / 60);
$seconds = $average_time % 60;
echo $minutes . 'm ' . $seconds . 's';
} else {
echo 'N/A';
}
?>
</h3>
<p>Average Time</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">👁️</div>
<div class="stat-details">
<h3><?php echo number_format($survey['views']); ?></h3>
<p>Total Views</p>
</div>
</div>
</div>
<!-- Responses Over Time Chart -->
<div class="card">
<div class="card-header">
<h3>Responses Over Time</h3>
</div>
<div class="card-body">
<canvas id="responsesChart" width="400" height="200"></canvas>
</div>
</div>
<!-- Question Analysis -->
<h2>Question Analysis</h2>
<?php foreach ($survey_questions as $question): ?>
<div class="card">
<div class="card-header">
<h3><?php echo $question['question']; ?></h3>
<span class="badge"><?php echo $question['answer_count']; ?> responses</span>
</div>
<div class="card-body">
<?php if (empty($answers_data[$question['id']])): ?>
<p class="text-muted">No responses yet for this question.</p>
<?php else: ?>
<?php if (in_array($question['type'], ['radio', 'select'])): ?>
<!-- Pie/Bar chart for single choice questions -->
<canvas id="chart-<?php echo $question['id']; ?>" width="400" height="200"></canvas>
<script>
new Chart(document.getElementById('chart-<?php echo $question['id']; ?>'), {
type: 'pie',
data: {
labels: <?php echo json_encode(array_column($answers_data[$question['id']], 'answer')); ?>,
datasets: [{
data: <?php echo json_encode(array_column($answers_data[$question['id']], 'count')); ?>,
backgroundColor: [
'#3498db', '#e74c3c', '#2ecc71', '#f39c12', 
'#9b59b6', '#1abc9c', '#e67e22', '#34495e'
]
}]
}
});
</script>
<?php elseif ($question['type'] == 'checkbox'): ?>
<!-- Multiple choice - show counts -->
<table class="data-table">
<thead>
<tr>
<th>Option</th>
<th>Count</th>
<th>Percentage</th>
</tr>
</thead>
<tbody>
<?php 
$options = json_decode($question['options'], true);
$total = array_sum(array_column($answers_data[$question['id']], 'count'));
foreach ($options as $option):
$found = false;
foreach ($answers_data[$question['id']] as $answer) {
$answer_text = $answer['answer'];
// Check if answer contains this option (for checkbox)
if (strpos($answer_text, $option) !== false) {
$found = true;
$count = $answer['count'];
$percentage = round(($count / $total) * 100, 1);
break;
}
}
if (!$found) {
$count = 0;
$percentage = 0;
}
?>
<tr>
<td><?php echo $option; ?></td>
<td><?php echo $count; ?></td>
<td><?php echo $percentage; ?>%</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php elseif ($question['type'] == 'scale'): ?>
<!-- Scale visualization -->
<div class="scale-results">
<?php 
$min = $question['scale_min'];
$max = $question['scale_max'];
$total = array_sum(array_column($answers_data[$question['id']], 'count'));
for ($i = $min; $i <= $max; $i++):
$found = false;
foreach ($answers_data[$question['id']] as $answer) {
if ($answer['answer'] == $i) {
$found = true;
$count = $answer['count'];
$percentage = round(($count / $total) * 100, 1);
break;
}
}
if (!$found) {
$count = 0;
$percentage = 0;
}
?>
<div class="scale-item">
<div class="scale-label"><?php echo $i; ?></div>
<div class="scale-bar-container">
<div class="scale-bar" style="width: <?php echo $percentage; ?>%"></div>
</div>
<div class="scale-value"><?php echo $count; ?> (<?php echo $percentage; ?>%)</div>
</div>
<?php endfor; ?>
</div>
<?php else: ?>
<!-- Text answers - show recent responses -->
<div class="text-responses">
<?php 
$text_answers = $pdo->prepare("
SELECT a.answer, r.completed_at
FROM answers a
JOIN responses r ON a.response_id = r.id
WHERE a.question_id = ?
ORDER BY r.completed_at DESC
LIMIT 10
");
$text_answers->execute([$question['id']]);
$recent_text = $text_answers->fetchAll();
?>
<?php foreach ($recent_text as $answer): ?>
<div class="text-response-item">
<p><?php echo nl2br($answer['answer']); ?></p>
<small><?php echo timeAgo($answer['completed_at']); ?></small>
</div>
<?php endforeach; ?>
<?php if (count($recent_text) < $question['answer_count']): ?>
<p class="text-muted">
Showing 10 of <?php echo $question['answer_count']; ?> responses.
<a href="responses.php?survey=<?php echo $survey_id; ?>&question=<?php echo $question['id']; ?>">View all</a>
</p>
<?php endif; ?>
</div>
<?php endif; ?>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</div>
</main>
</div>
<script>
// Responses over time chart
new Chart(document.getElementById('responsesChart'), {
type: 'line',
data: {
labels: <?php echo json_encode(array_column($time_data, 'date')); ?>,
datasets: [{
label: 'Responses',
data: <?php echo json_encode(array_column($time_data, 'count')); ?>,
borderColor: '#3498db',
backgroundColor: 'rgba(52, 152, 219, 0.1)',
tension: 0.1
}]
},
options: {
responsive: true,
maintainAspectRatio: false
}
});
</script>
</body>
</html>

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

/* Main Styles for Online Survey System */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background: #f8f9fa;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
/* Header Styles */
.site-header {
background: #fff;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
position: sticky;
top: 0;
z-index: 1000;
}
.site-header .container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 20px;
}
.logo h1 {
font-size: 1.8rem;
color: #2c3e50;
font-weight: 700;
}
.logo a {
text-decoration: none;
color: inherit;
}
.main-nav ul {
display: flex;
list-style: none;
gap: 2rem;
}
.main-nav a {
text-decoration: none;
color: #333;
font-weight: 500;
transition: color 0.3s;
}
.main-nav a:hover,
.main-nav a.active {
color: #3498db;
}
.auth-buttons {
display: flex;
gap: 1rem;
}
.btn {
display: inline-block;
padding: 0.5rem 1rem;
border-radius: 4px;
text-decoration: none;
font-weight: 500;
transition: all 0.3s;
border: none;
cursor: pointer;
font-size: 0.9rem;
}
.btn-primary {
background: #3498db;
color: #fff;
}
.btn-primary:hover {
background: #2980b9;
}
.btn-success {
background: #27ae60;
color: #fff;
}
.btn-success:hover {
background: #229954;
}
.btn-large {
padding: 1rem 2rem;
font-size: 1.1rem;
}
.btn-block {
display: block;
width: 100%;
text-align: center;
}
/* Hero Section */
.hero {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
padding: 4rem 0;
text-align: center;
}
.hero h1 {
font-size: 3rem;
margin-bottom: 1rem;
}
.hero p {
font-size: 1.2rem;
margin-bottom: 2rem;
opacity: 0.9;
max-width: 600px;
margin-left: auto;
margin-right: auto;
}
.hero-stats {
display: flex;
justify-content: center;
gap: 3rem;
margin: 3rem 0;
}
.stat {
text-align: center;
}
.stat-value {
display: block;
font-size: 2rem;
font-weight: bold;
}
.stat-label {
font-size: 0.9rem;
opacity: 0.9;
}
.hero-actions {
display: flex;
gap: 1rem;
justify-content: center;
}
/* Features Section */
.features {
padding: 4rem 0;
background: #fff;
}
.section-title {
text-align: center;
font-size: 2.5rem;
color: #2c3e50;
margin-bottom: 3rem;
position: relative;
}
.section-title:after {
content: '';
position: absolute;
bottom: -10px;
left: 50%;
transform: translateX(-50%);
width: 60px;
height: 3px;
background: #3498db;
}
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
}
.feature-card {
text-align: center;
padding: 2rem;
border-radius: 8px;
background: #f8f9fa;
transition: transform 0.3s;
}
.feature-card:hover {
transform: translateY(-5px);
}
.feature-icon {
font-size: 3rem;
margin-bottom: 1rem;
}
.feature-card h3 {
margin-bottom: 1rem;
color: #2c3e50;
}
/* Categories Section */
.categories {
padding: 4rem 0;
background: #f8f9fa;
}
.categories-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.5rem;
}
.category-card {
background: #fff;
padding: 1.5rem;
border-radius: 8px;
text-decoration: none;
color: inherit;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
transition: transform 0.3s;
border-top: 4px solid #3498db;
}
.category-card:hover {
transform: translateY(-5px);
}
.category-card h3 {
margin-bottom: 0.5rem;
color: #2c3e50;
}
.category-card p {
color: #666;
font-size: 0.9rem;
}
/* Surveys Grid */
.public-surveys {
padding: 4rem 0;
background: #fff;
}
.surveys-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 2rem;
margin-bottom: 2rem;
}
.survey-card {
background: #fff;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
transition: transform 0.3s;
}
.survey-card:hover {
transform: translateY(-5px);
}
.survey-header {
padding: 1rem;
color: #fff;
display: flex;
justify-content: space-between;
align-items: center;
}
.survey-category {
font-size: 0.8rem;
opacity: 0.9;
}
.survey-status {
padding: 0.25rem 0.5rem;
border-radius: 3px;
font-size: 0.75rem;
font-weight: bold;
}
.status-active {
background: #27ae60;
}
.status-draft {
background: #95a5a6;
}
.status-closed {
background: #e74c3c;
}
.survey-body {
padding: 1.5rem;
}
.survey-title {
font-size: 1.2rem;
margin-bottom: 0.5rem;
color: #2c3e50;
}
.survey-description {
color: #666;
font-size: 0.9rem;
margin-bottom: 1rem;
}
.survey-meta {
display: flex;
justify-content: space-between;
font-size: 0.8rem;
color: #999;
}
.survey-footer {
padding: 1rem;
border-top: 1px solid #eee;
}
/* Survey Taking Page */
.survey-container {
min-height: 100vh;
background: var(--background-color);
}
.survey-header {
padding: 2rem 0;
color: #fff;
text-align: center;
}
.survey-logo {
max-height: 60px;
margin-bottom: 1rem;
}
.welcome-message {
max-width: 600px;
margin: 1rem auto 0;
opacity: 0.9;
}
.progress-container {
width: 100%;
height: 4px;
background: rgba(255,255,255,0.3);
border-radius: 2px;
margin: 1rem 0;
}
.progress-bar {
height: 100%;
background: #27ae60;
border-radius: 2px;
transition: width 0.3s;
}
.progress-text {
font-size: 0.9rem;
opacity: 0.9;
}
.survey-form {
max-width: 700px;
margin: 2rem auto;
background: #fff;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 2px 20px rgba(0,0,0,0.1);
}
.question-card {
margin-bottom: 2rem;
}
.question-number {
font-size: 0.9rem;
color: #666;
margin-bottom: 0.5rem;
}
.question-text {
font-size: 1.2rem;
margin-bottom: 0.5rem;
font-weight: 500;
}
.required {
color: #e74c3c;
margin-left: 0.25rem;
}
.question-description {
font-size: 0.9rem;
color: #666;
margin-bottom: 1rem;
}
.question-input {
margin-bottom: 1.5rem;
}
.form-control {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
transition: border-color 0.3s;
}
.form-control:focus {
outline: none;
border-color: var(--primary-color);
}
.radio-option,
.checkbox-option {
margin-bottom: 0.5rem;
}
.radio-option label,
.checkbox-option label {
display: flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
}
.rating {
display: flex;
gap: 0.5rem;
}
.star {
font-size: 2rem;
cursor: pointer;
color: #ddd;
}
.star:hover,
.star.selected {
color: #f39c12;
}
.scale {
display: flex;
justify-content: space-between;
margin: 1rem 0;
}
.scale-option {
text-align: center;
flex: 1;
}
.scale-option input {
margin-bottom: 0.25rem;
}
.scale-label {
display: block;
font-size: 0.8rem;
color: #666;
}
.question-navigation {
display: flex;
justify-content: space-between;
margin-top: 2rem;
padding-top: 1rem;
border-top: 1px solid #eee;
}
/* CTA Section */
.cta {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
text-align: center;
padding: 4rem 0;
}
.cta h2 {
font-size: 2.5rem;
margin-bottom: 1rem;
}
.cta p {
font-size: 1.2rem;
margin-bottom: 2rem;
opacity: 0.9;
}
/* Footer */
.site-footer {
background: #2c3e50;
color: #fff;
padding: 3rem 0 1rem;
}
.footer-widgets {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
margin-bottom: 2rem;
}
.footer-widget h4 {
color: #fff;
margin-bottom: 1rem;
}
.footer-widget p {
color: #bdc3c7;
line-height: 1.6;
}
.footer-widget ul {
list-style: none;
}
.footer-widget li {
margin-bottom: 0.5rem;
}
.footer-widget a {
color: #bdc3c7;
text-decoration: none;
transition: color 0.3s;
}
.footer-widget a:hover {
color: #fff;
}
.footer-bottom {
text-align: center;
padding-top: 2rem;
border-top: 1px solid #34495e;
color: #bdc3c7;
}
/* Alerts */
.alert {
padding: 1rem;
border-radius: 4px;
margin-bottom: 1rem;
}
.alert-success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.alert-error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
/* Text Utilities */
.text-center {
text-align: center;
}
.text-muted {
color: #666;
}
/* Responsive */
@media (max-width: 768px) {
.site-header .container {
flex-direction: column;
gap: 1rem;
}
.main-nav ul {
flex-wrap: wrap;
justify-content: center;
}
.auth-buttons {
justify-content: center;
}
.hero h1 {
font-size: 2rem;
}
.hero-stats {
flex-direction: column;
gap: 1rem;
}
.hero-actions {
flex-direction: column;
}
.features-grid,
.categories-grid,
.surveys-grid {
grid-template-columns: 1fr;
}
.scale {
flex-direction: column;
gap: 1rem;
}
.question-navigation {
flex-direction: column;
gap: 1rem;
}
}

📝 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 online-survey-system

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/survey_system.sql
  4. Paste and click "Go" to create database and tables

Step 4: Create Password Hashes

Create a file named hash.php in project root:

<?php
echo "admin123 hash: " . password_hash('admin123', PASSWORD_DEFAULT) . "\n";
echo "user123 hash: " . password_hash('user123', PASSWORD_DEFAULT);
?>

Run it: http://localhost/online-survey-system/hash.php
Copy the hashes and update them in the SQL insert statements for the users.

Step 5: Configure Database Connection

Open includes/config.php and verify:

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

Step 6: Test the Application

Public Side:

  • Open: http://localhost/online-survey-system/
  • Browse public surveys
  • Take a sample survey

User Account:

  • Login with: Username: john.doe, Password: user123
  • Create your own surveys
  • Add questions
  • Share survey links
  • View responses and analytics

Admin Account:

  • Login with: Username: admin, Password: admin123
  • Manage all users
  • View all surveys
  • Manage categories
  • System settings

Step 7: Create Your First Survey

  1. Login as regular user
  2. Go to DashboardCreate Survey
  3. Fill in survey details
  4. Add questions with various types
  5. Publish the survey
  6. Share the unique link with respondents

Step 8: Collect and Analyze Responses

  1. Share your survey link via email or social media
  2. Watch responses come in real-time
  3. View analytics with charts and graphs
  4. Export data to CSV for further analysis

🎯 Features Summary

Survey Creator Features:

  • ✅ Create unlimited surveys
  • ✅ Multiple question types (11+ types)
  • ✅ Customizable themes and colors
  • ✅ Set survey start/end dates
  • ✅ Limit responses per person
  • ✅ Collect respondent information
  • ✅ Progress bar and question numbers
  • ✅ Real-time response tracking

Respondent Features:

  • ✅ Clean, mobile-friendly interface
  • ✅ Progress indicator
  • ✅ Save and continue (session-based)
  • ✅ Confirmation page
  • ✅ Email thank you (optional)

Analytics Features:

  • ✅ Response statistics dashboard
  • ✅ Visual charts (pie, bar, line)
  • ✅ Response over time graphs
  • ✅ Question-by-question analysis
  • ✅ Export to CSV
  • ✅ Completion rate tracking
  • ✅ Average time to complete

Admin Features:

  • ✅ User management
  • ✅ Survey moderation
  • ✅ Category management
  • ✅ System settings
  • ✅ View all surveys
  • ✅ Generate reports

Technical Features:

  • ✅ Secure authentication
  • ✅ Password hashing
  • ✅ SQL injection prevention
  • ✅ XSS protection
  • ✅ Responsive design
  • ✅ AJAX for smooth experience
  • ✅ Session management
  • ✅ Email notifications

🚀 Future Enhancements

  1. Advanced Logic: Skip logic, branching, conditional questions
  2. Templates: Pre-built survey templates
  3. Team Collaboration: Multiple collaborators per survey
  4. API Access: REST API for integrations
  5. White Label: Custom branding options
  6. Offline Mode: Take surveys offline, sync later
  7. QR Codes: Generate QR codes for survey links
  8. Social Media Integration: Direct sharing to social platforms
  9. Payment Integration: Paid surveys
  10. Multi-language Support: Surveys in multiple languages
  11. Advanced Analytics: Statistical analysis, cross-tabulation
  12. Custom Reports: Scheduled email reports
  13. Response Validation: Advanced validation rules
  14. File Uploads: Respondents can upload files
  15. Survey Embed: Embed surveys in websites
  16. Mobile App: Native iOS/Android apps
  17. Webhook Integration: Send responses to other systems
  18. GDPR Compliance: Data privacy features
  19. A/B Testing: Test different survey versions
  20. AI Analysis: Sentiment analysis on text responses

This comprehensive Online Survey System provides a complete solution for creating, distributing, and analyzing surveys for any purpose!

Leave a Reply

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


Macro Nepal Helper