feat: Add complete Docker deployment with web-based setup wizard
Major additions: - Web-based setup wizard (setup.php, setup_wizard.php, setup-wizard.js) - Production Docker configuration (docker-compose.prod.yml, .env.production) - Database initialization SQL files (deploy/init_settings.sql) - Template builder system with drag-and-drop UI - Advanced features (OAuth, CDN, enhanced analytics, monetization) - Comprehensive documentation (deployment guides, quick start, feature docs) - Design system with accessibility and responsive layout - Deployment automation scripts (deploy.ps1, generate-secrets.ps1) Setup wizard allows customization of: - Platform name and branding - Domain configuration - Membership tiers and pricing - Admin credentials - Feature toggles Database includes 270+ tables for complete video streaming platform with advanced features for analytics, moderation, template building, and monetization. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
173
f_modules/m_frontend/index.php
Normal file
173
f_modules/m_frontend/index.php
Normal file
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
/*******************************************************************************************************************
|
||||
| Software Name : EasyStream
|
||||
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
|
||||
| Software Author : (c) Sami Ahmed
|
||||
|*******************************************************************************************************************
|
||||
|
|
||||
|*******************************************************************************************************************
|
||||
| This source file is subject to the EasyStream Proprietary License Agreement.
|
||||
|
|
||||
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|
||||
|*******************************************************************************************************************
|
||||
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|
||||
|*******************************************************************************************************************/
|
||||
|
||||
defined('_INCLUDE') or header('Location: /error');
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>EasyStream - Video Streaming Platform</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
||||
padding: 60px 40px;
|
||||
max-width: 600px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 30px;
|
||||
font-size: 40px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
margin-bottom: 15px;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: #666;
|
||||
margin-bottom: 30px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.status {
|
||||
background: #f0f4ff;
|
||||
border: 2px solid #667eea;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 30px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.status h3 {
|
||||
color: #667eea;
|
||||
margin-bottom: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.status-item::before {
|
||||
content: '✓';
|
||||
color: #48bb78;
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 12px 30px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: white;
|
||||
color: #667eea;
|
||||
border: 2px solid #667eea;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #f0f4ff;
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="logo">▶</div>
|
||||
|
||||
<h1>Welcome to EasyStream</h1>
|
||||
<p class="subtitle">Your Video Streaming Platform is Ready</p>
|
||||
|
||||
<div class="status">
|
||||
<h3>System Status</h3>
|
||||
<div class="status-item">Database Connected</div>
|
||||
<div class="status-item">Setup Complete</div>
|
||||
<div class="status-item">All Services Running</div>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<a href="/signin" class="btn btn-primary">Sign In</a>
|
||||
<a href="/signup" class="btn btn-secondary">Create Account</a>
|
||||
</div>
|
||||
|
||||
<p class="footer-text">EasyStream v1.0 | © 2025 Sami Ahmed</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -80,6 +80,11 @@ if (intval($_POST["frontend_global_submit"] == 1) and $cfg["frontend_signin_sect
|
||||
$remember = ($cfg["login_remember"] == 1 and $cfg["frontend_signin_section"] == 1) ? VLoginRemember::checkLogin('frontend') : null;
|
||||
$logged_in = VLogin::isLoggedIn();
|
||||
|
||||
// Assign config values to smarty template
|
||||
$smarty->assign('frontend_signin_section', $cfg["frontend_signin_section"]);
|
||||
$smarty->assign('login_remember', $cfg["login_remember"]);
|
||||
$smarty->assign('signin_captcha', intval($cfg["signin_captcha"] ?? 0));
|
||||
|
||||
$class_smarty->displayPage('frontend', 'tpl_signin', $error_message, $notice_message);
|
||||
|
||||
$_SESSION["USER_ERROR"] = null;
|
||||
|
||||
72
f_modules/m_frontend/templatebuilder.php
Normal file
72
f_modules/m_frontend/templatebuilder.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/**
|
||||
* Template Builder Module
|
||||
* Handles the template builder interface for users
|
||||
*/
|
||||
|
||||
// Check if _ISVALID is already defined (it should be by parser.php)
|
||||
if (!defined('_ISVALID')) {
|
||||
define('_ISVALID', true);
|
||||
}
|
||||
|
||||
$main_dir = realpath(dirname(__FILE__) . '/../../../');
|
||||
set_include_path($main_dir);
|
||||
|
||||
require_once 'f_core/config.core.php';
|
||||
|
||||
// Verify user is logged in - this will redirect to signin if not
|
||||
VLogin::checkFrontend('builder');
|
||||
|
||||
// Get user ID from session
|
||||
$_user_id = isset($_SESSION['USER_ID']) ? (int)$_SESSION['USER_ID'] : 0;
|
||||
|
||||
// Load configuration
|
||||
$cfg_builder = $class_database->getConfigurations('template_builder_enabled,template_builder_max_templates,template_builder_mode');
|
||||
|
||||
// Check if template builder is enabled
|
||||
if (!isset($cfg_builder['template_builder_enabled']) || $cfg_builder['template_builder_enabled'] != 1) {
|
||||
header('Location: ' . $cfg['main_url'] . '/error?code=feature_disabled');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Load language files
|
||||
include_once $class_language->setLanguageFile('frontend', 'language.builder');
|
||||
|
||||
// Initialize template builder
|
||||
require_once 'f_core/f_classes/class.templatebuilder.php';
|
||||
$templateBuilder = new VTemplateBuilder();
|
||||
|
||||
// Get user's templates
|
||||
$user_templates = $templateBuilder->getUserTemplates([
|
||||
'user_id' => $_user_id,
|
||||
'limit' => 100
|
||||
]);
|
||||
|
||||
// Get available components
|
||||
$available_components = $templateBuilder->getComponents();
|
||||
|
||||
// Get user preferences
|
||||
$user_prefs = $templateBuilder->getUserPreferences($_user_id);
|
||||
|
||||
// Assign to Smarty template
|
||||
$smarty->assign('template_list', $user_templates);
|
||||
$smarty->assign('available_components', $available_components);
|
||||
$smarty->assign('user_preferences', $user_prefs);
|
||||
$smarty->assign('builder_enabled', true);
|
||||
$smarty->assign('max_templates', $cfg_builder['template_builder_max_templates'] ?? 10);
|
||||
$smarty->assign('builder_mode', $cfg_builder['template_builder_mode'] ?? 'simple');
|
||||
|
||||
// Load template editor if template ID provided
|
||||
if (isset($_GET['template_id'])) {
|
||||
$template_id = (int)$_GET['template_id'];
|
||||
$template = $templateBuilder->getTemplate($template_id);
|
||||
|
||||
if ($template) {
|
||||
$smarty->assign('current_template', $template);
|
||||
$smarty->assign('template_id', $template_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Display the builder interface using the proper method
|
||||
$class_smarty->displayPage('frontend', 'tpl_builder');
|
||||
?>
|
||||
328
f_modules/m_frontend/templatebuilder_ajax.php
Normal file
328
f_modules/m_frontend/templatebuilder_ajax.php
Normal file
@@ -0,0 +1,328 @@
|
||||
<?php
|
||||
/**
|
||||
* Template Builder AJAX Handler
|
||||
* Handles all AJAX requests for the template builder
|
||||
*/
|
||||
|
||||
// Include core configuration
|
||||
require_once dirname(__FILE__) . '/../../f_core/config.core.php';
|
||||
|
||||
// Check if user is logged in
|
||||
if (!isset($_SESSION['USER_ID']) || $_SESSION['USER_ID'] <= 0) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Initialize template builder class
|
||||
require_once $cfg['classes_dir'] . '/class.templatebuilder.php';
|
||||
$templateBuilder = new VTemplateBuilder();
|
||||
|
||||
// Get request data
|
||||
$action = isset($_GET['action']) ? $_GET['action'] : (isset($_POST['action']) ? $_POST['action'] : '');
|
||||
|
||||
// Handle different actions
|
||||
switch ($action) {
|
||||
case 'get_components':
|
||||
handleGetComponents($templateBuilder);
|
||||
break;
|
||||
|
||||
case 'create_template':
|
||||
handleCreateTemplate($templateBuilder);
|
||||
break;
|
||||
|
||||
case 'update_template':
|
||||
handleUpdateTemplate($templateBuilder);
|
||||
break;
|
||||
|
||||
case 'delete_template':
|
||||
handleDeleteTemplate($templateBuilder);
|
||||
break;
|
||||
|
||||
case 'get_template':
|
||||
handleGetTemplate($templateBuilder);
|
||||
break;
|
||||
|
||||
case 'get_templates':
|
||||
handleGetTemplates($templateBuilder);
|
||||
break;
|
||||
|
||||
case 'publish_template':
|
||||
handlePublishTemplate($templateBuilder);
|
||||
break;
|
||||
|
||||
case 'duplicate_template':
|
||||
handleDuplicateTemplate($templateBuilder);
|
||||
break;
|
||||
|
||||
case 'preview':
|
||||
handlePreview($templateBuilder);
|
||||
break;
|
||||
|
||||
case 'render':
|
||||
handleRender($templateBuilder);
|
||||
break;
|
||||
|
||||
default:
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid action']);
|
||||
break;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available components
|
||||
*/
|
||||
function handleGetComponents($templateBuilder)
|
||||
{
|
||||
$category = isset($_GET['category']) ? $_GET['category'] : null;
|
||||
$components = $templateBuilder->getComponents($category);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'components' => $components
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new template
|
||||
*/
|
||||
function handleCreateTemplate($templateBuilder)
|
||||
{
|
||||
$data = getJsonInput();
|
||||
|
||||
if (!$data) {
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid request data']);
|
||||
return;
|
||||
}
|
||||
|
||||
$result = $templateBuilder->createTemplate($data);
|
||||
echo json_encode($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update existing template
|
||||
*/
|
||||
function handleUpdateTemplate($templateBuilder)
|
||||
{
|
||||
$data = getJsonInput();
|
||||
|
||||
if (!$data || !isset($data['template_id'])) {
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid request data']);
|
||||
return;
|
||||
}
|
||||
|
||||
$templateId = (int)$data['template_id'];
|
||||
$changeNote = isset($data['change_note']) ? $data['change_note'] : null;
|
||||
|
||||
$result = $templateBuilder->updateTemplate($templateId, $data, $changeNote);
|
||||
echo json_encode($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete template
|
||||
*/
|
||||
function handleDeleteTemplate($templateBuilder)
|
||||
{
|
||||
$data = getJsonInput();
|
||||
|
||||
if (!$data || !isset($data['template_id'])) {
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid request data']);
|
||||
return;
|
||||
}
|
||||
|
||||
$templateId = (int)$data['template_id'];
|
||||
$result = $templateBuilder->deleteTemplate($templateId);
|
||||
echo json_encode($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get template by ID
|
||||
*/
|
||||
function handleGetTemplate($templateBuilder)
|
||||
{
|
||||
$templateId = isset($_GET['template_id']) ? (int)$_GET['template_id'] : 0;
|
||||
|
||||
if ($templateId <= 0) {
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid template ID']);
|
||||
return;
|
||||
}
|
||||
|
||||
$template = $templateBuilder->getTemplate($templateId);
|
||||
|
||||
if ($template) {
|
||||
echo json_encode(['success' => true, 'template' => $template]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'error' => 'Template not found']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all templates for current user
|
||||
*/
|
||||
function handleGetTemplates($templateBuilder)
|
||||
{
|
||||
$filters = [];
|
||||
|
||||
if (isset($_GET['template_type'])) {
|
||||
$filters['template_type'] = $_GET['template_type'];
|
||||
}
|
||||
|
||||
if (isset($_GET['is_active'])) {
|
||||
$filters['is_active'] = (int)$_GET['is_active'];
|
||||
}
|
||||
|
||||
if (isset($_GET['limit'])) {
|
||||
$filters['limit'] = (int)$_GET['limit'];
|
||||
}
|
||||
|
||||
$templates = $templateBuilder->getUserTemplates($filters);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'templates' => $templates,
|
||||
'count' => count($templates)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish template (make active)
|
||||
*/
|
||||
function handlePublishTemplate($templateBuilder)
|
||||
{
|
||||
$data = getJsonInput();
|
||||
|
||||
if (!$data || !isset($data['template_id'])) {
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid request data']);
|
||||
return;
|
||||
}
|
||||
|
||||
$templateId = (int)$data['template_id'];
|
||||
|
||||
$result = $templateBuilder->updateTemplate($templateId, [
|
||||
'is_active' => 1
|
||||
], 'Published template');
|
||||
|
||||
echo json_encode($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate template
|
||||
*/
|
||||
function handleDuplicateTemplate($templateBuilder)
|
||||
{
|
||||
$data = getJsonInput();
|
||||
|
||||
if (!$data || !isset($data['template_id'])) {
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid request data']);
|
||||
return;
|
||||
}
|
||||
|
||||
$templateId = (int)$data['template_id'];
|
||||
$newName = isset($data['new_name']) ? $data['new_name'] : null;
|
||||
|
||||
$result = $templateBuilder->duplicateTemplate($templateId, $newName);
|
||||
echo json_encode($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview template
|
||||
*/
|
||||
function handlePreview($templateBuilder)
|
||||
{
|
||||
$templateId = isset($_GET['template_id']) ? (int)$_GET['template_id'] : 0;
|
||||
|
||||
if ($templateId <= 0) {
|
||||
echo 'Invalid template ID';
|
||||
return;
|
||||
}
|
||||
|
||||
$template = $templateBuilder->getTemplate($templateId, false);
|
||||
|
||||
if (!$template) {
|
||||
echo 'Template not found';
|
||||
return;
|
||||
}
|
||||
|
||||
// Render template
|
||||
$html = $templateBuilder->renderTemplate($templateId);
|
||||
|
||||
// Output as HTML page
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Preview: <?php echo htmlspecialchars($template['template_name']); ?></title>
|
||||
<link rel="stylesheet" href="<?php echo $cfg['styles_url']; ?>/init0.min.css">
|
||||
<link rel="stylesheet" href="<?php echo $cfg['styles_url']; ?>/theme/theme.min.css">
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.preview-container {
|
||||
max-width: <?php echo $template['template_structure']['max_width'] ?? 1200; ?>px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="preview-container">
|
||||
<?php echo $html; ?>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Render template (for frontend display)
|
||||
*/
|
||||
function handleRender($templateBuilder)
|
||||
{
|
||||
$templateId = isset($_GET['template_id']) ? (int)$_GET['template_id'] : 0;
|
||||
$slug = isset($_GET['slug']) ? $_GET['slug'] : '';
|
||||
|
||||
if ($templateId > 0) {
|
||||
$html = $templateBuilder->renderTemplate($templateId);
|
||||
} elseif ($slug) {
|
||||
$html = $templateBuilder->renderTemplate($slug);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'error' => 'Template ID or slug required']);
|
||||
return;
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'html' => $html
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get JSON input from request body
|
||||
*/
|
||||
function getJsonInput()
|
||||
{
|
||||
$input = file_get_contents('php://input');
|
||||
|
||||
if (empty($input)) {
|
||||
return $_POST;
|
||||
}
|
||||
|
||||
$data = json_decode($input, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
VLogger::log('ERROR', 'JSON decode error in template builder AJAX', [
|
||||
'error' => json_last_error_msg(),
|
||||
'input' => substr($input, 0, 1000)
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
Reference in New Issue
Block a user