- Created complete documentation in docs/ directory - Added PROJECT_OVERVIEW.md with feature highlights and getting started guide - Added ARCHITECTURE.md with system design and technical details - Added SECURITY.md with comprehensive security implementation guide - Added DEVELOPMENT.md with development workflows and best practices - Added DEPLOYMENT.md with production deployment instructions - Added API.md with complete REST API documentation - Added CONTRIBUTING.md with contribution guidelines - Added CHANGELOG.md with version history and migration notes - Reorganized all documentation files into docs/ directory for better organization - Updated README.md with proper documentation links and quick navigation - Enhanced project structure with professional documentation standards
650 lines
24 KiB
PHP
650 lines
24 KiB
PHP
<?php
|
|
/*******************************************************************************************************************
|
|
| EasyStream Token Customization System
|
|
| Allows platform owners to customize their monetization token branding
|
|
|*******************************************************************************************************************/
|
|
|
|
define('_ISVALID', true);
|
|
include_once '../../f_core/config.core.php';
|
|
|
|
// Check admin permissions
|
|
$logged_in = VLogin::checkBackend('token_customization');
|
|
|
|
$error_message = '';
|
|
$success_message = '';
|
|
|
|
// Handle form submission
|
|
if ($_POST && isset($_POST['action'])) {
|
|
switch ($_POST['action']) {
|
|
case 'update_token_settings':
|
|
$result = updateTokenSettings($_POST);
|
|
if ($result['success']) {
|
|
$success_message = $result['message'];
|
|
} else {
|
|
$error_message = $result['message'];
|
|
}
|
|
break;
|
|
|
|
case 'upload_token_icon':
|
|
$result = uploadTokenIcon($_FILES['token_icon']);
|
|
if ($result['success']) {
|
|
$success_message = $result['message'];
|
|
} else {
|
|
$error_message = $result['message'];
|
|
}
|
|
break;
|
|
|
|
case 'reset_to_defaults':
|
|
$result = resetTokenDefaults();
|
|
if ($result['success']) {
|
|
$success_message = $result['message'];
|
|
} else {
|
|
$error_message = $result['message'];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Get current token settings
|
|
$token_settings = getTokenSettings();
|
|
|
|
function getTokenSettings() {
|
|
global $class_database;
|
|
|
|
$defaults = [
|
|
'token_name' => 'EasyCoins',
|
|
'token_symbol' => 'EC',
|
|
'token_plural' => 'EasyCoins',
|
|
'token_description' => 'Platform currency for tips and donations',
|
|
'token_icon' => '/f_templates/tpl_frontend/img/default-token.png',
|
|
'token_color_primary' => '#FFD700',
|
|
'token_color_secondary' => '#FFA500',
|
|
'exchange_rate' => '1.00',
|
|
'min_purchase' => '1.00',
|
|
'max_purchase' => '1000.00',
|
|
'enabled' => '1'
|
|
];
|
|
|
|
try {
|
|
$sql = "SELECT setting_key, setting_value FROM db_settings WHERE setting_key LIKE 'token_%'";
|
|
$result = $class_database->execute($sql);
|
|
|
|
$settings = $defaults;
|
|
while (!$result->EOF) {
|
|
$key = str_replace('token_', '', $result->fields['setting_key']);
|
|
$settings[$key] = $result->fields['setting_value'];
|
|
$result->MoveNext();
|
|
}
|
|
|
|
return $settings;
|
|
} catch (Exception $e) {
|
|
return $defaults;
|
|
}
|
|
}
|
|
|
|
function updateTokenSettings($data) {
|
|
global $class_database;
|
|
|
|
try {
|
|
$settings = [
|
|
'token_name' => $class_filter->clr_str($data['token_name']),
|
|
'token_symbol' => strtoupper($class_filter->clr_str($data['token_symbol'])),
|
|
'token_plural' => $class_filter->clr_str($data['token_plural']),
|
|
'token_description' => $class_filter->clr_str($data['token_description']),
|
|
'token_color_primary' => $class_filter->clr_str($data['token_color_primary']),
|
|
'token_color_secondary' => $class_filter->clr_str($data['token_color_secondary']),
|
|
'exchange_rate' => floatval($data['exchange_rate']),
|
|
'min_purchase' => floatval($data['min_purchase']),
|
|
'max_purchase' => floatval($data['max_purchase']),
|
|
'enabled' => isset($data['enabled']) ? '1' : '0'
|
|
];
|
|
|
|
foreach ($settings as $key => $value) {
|
|
$sql = "INSERT INTO db_settings (setting_key, setting_value, setting_description)
|
|
VALUES (?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE setting_value = ?";
|
|
|
|
$description = ucfirst(str_replace('_', ' ', $key));
|
|
$class_database->execute($sql, ["token_$key", $value, $description, $value]);
|
|
}
|
|
|
|
return ['success' => true, 'message' => 'Token settings updated successfully!'];
|
|
|
|
} catch (Exception $e) {
|
|
return ['success' => false, 'message' => 'Error updating settings: ' . $e->getMessage()];
|
|
}
|
|
}
|
|
|
|
function uploadTokenIcon($file) {
|
|
if (!isset($file['tmp_name']) || $file['error'] !== UPLOAD_ERR_OK) {
|
|
return ['success' => false, 'message' => 'No file uploaded or upload error'];
|
|
}
|
|
|
|
// Validate file type
|
|
$allowed_types = ['image/png', 'image/jpeg', 'image/gif', 'image/svg+xml'];
|
|
if (!in_array($file['type'], $allowed_types)) {
|
|
return ['success' => false, 'message' => 'Invalid file type. Please upload PNG, JPG, GIF, or SVG'];
|
|
}
|
|
|
|
// Validate file size (max 2MB)
|
|
if ($file['size'] > 2 * 1024 * 1024) {
|
|
return ['success' => false, 'message' => 'File too large. Maximum size is 2MB'];
|
|
}
|
|
|
|
try {
|
|
$upload_dir = 'f_templates/tpl_frontend/img/tokens/';
|
|
if (!is_dir($upload_dir)) {
|
|
mkdir($upload_dir, 0755, true);
|
|
}
|
|
|
|
$extension = pathinfo($file['name'], PATHINFO_EXTENSION);
|
|
$filename = 'token_icon_' . time() . '.' . $extension;
|
|
$filepath = $upload_dir . $filename;
|
|
|
|
if (move_uploaded_file($file['tmp_name'], $filepath)) {
|
|
// Update database
|
|
global $class_database;
|
|
$sql = "INSERT INTO db_settings (setting_key, setting_value, setting_description)
|
|
VALUES ('token_icon', ?, 'Token icon image path')
|
|
ON DUPLICATE KEY UPDATE setting_value = ?";
|
|
|
|
$icon_path = '/' . $filepath;
|
|
$class_database->execute($sql, [$icon_path, $icon_path]);
|
|
|
|
return ['success' => true, 'message' => 'Token icon uploaded successfully!'];
|
|
} else {
|
|
return ['success' => false, 'message' => 'Failed to upload file'];
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
return ['success' => false, 'message' => 'Upload error: ' . $e->getMessage()];
|
|
}
|
|
}
|
|
|
|
function resetTokenDefaults() {
|
|
global $class_database;
|
|
|
|
try {
|
|
$sql = "DELETE FROM db_settings WHERE setting_key LIKE 'token_%'";
|
|
$class_database->execute($sql);
|
|
|
|
return ['success' => true, 'message' => 'Token settings reset to defaults!'];
|
|
|
|
} catch (Exception $e) {
|
|
return ['success' => false, 'message' => 'Error resetting settings: ' . $e->getMessage()];
|
|
}
|
|
}
|
|
?>
|
|
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Token Customization - EasyStream Admin</title>
|
|
<style>
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
margin: 0;
|
|
background: #f8f9fa;
|
|
color: #333;
|
|
}
|
|
|
|
.header {
|
|
background: linear-gradient(135deg, #667eea, #764ba2);
|
|
color: white;
|
|
padding: 20px 0;
|
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.header-content {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 0 20px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.container {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 30px 20px;
|
|
}
|
|
|
|
.card {
|
|
background: white;
|
|
border-radius: 12px;
|
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
margin-bottom: 30px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.card-header {
|
|
background: #f8f9fa;
|
|
padding: 20px 30px;
|
|
border-bottom: 1px solid #e9ecef;
|
|
font-size: 1.2rem;
|
|
font-weight: 600;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.card-body {
|
|
padding: 30px;
|
|
}
|
|
|
|
.form-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
gap: 20px;
|
|
}
|
|
|
|
.form-group {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.form-label {
|
|
display: block;
|
|
margin-bottom: 8px;
|
|
font-weight: 500;
|
|
color: #495057;
|
|
}
|
|
|
|
.form-input {
|
|
width: 100%;
|
|
padding: 12px;
|
|
border: 2px solid #e9ecef;
|
|
border-radius: 8px;
|
|
font-size: 1rem;
|
|
transition: border-color 0.2s ease;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.form-input:focus {
|
|
outline: none;
|
|
border-color: #667eea;
|
|
box-shadow: 0 0 0 3px rgba(102,126,234,0.1);
|
|
}
|
|
|
|
.color-input {
|
|
width: 100px;
|
|
height: 50px;
|
|
border: none;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.btn {
|
|
background: #667eea;
|
|
color: white;
|
|
border: none;
|
|
padding: 12px 24px;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
font-size: 1rem;
|
|
font-weight: 500;
|
|
transition: all 0.2s ease;
|
|
text-decoration: none;
|
|
display: inline-block;
|
|
}
|
|
|
|
.btn:hover {
|
|
background: #5a6fd8;
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.btn-secondary {
|
|
background: #6c757d;
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
background: #5a6268;
|
|
}
|
|
|
|
.btn-danger {
|
|
background: #dc3545;
|
|
}
|
|
|
|
.btn-danger:hover {
|
|
background: #c82333;
|
|
}
|
|
|
|
.alert {
|
|
padding: 15px;
|
|
border-radius: 8px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.alert-success {
|
|
background: #d4edda;
|
|
color: #155724;
|
|
border: 1px solid #c3e6cb;
|
|
}
|
|
|
|
.alert-error {
|
|
background: #f8d7da;
|
|
color: #721c24;
|
|
border: 1px solid #f5c6cb;
|
|
}
|
|
|
|
.token-preview {
|
|
background: linear-gradient(135deg, var(--token-primary, #FFD700), var(--token-secondary, #FFA500));
|
|
color: white;
|
|
padding: 20px;
|
|
border-radius: 12px;
|
|
text-align: center;
|
|
margin: 20px 0;
|
|
}
|
|
|
|
.token-icon {
|
|
width: 60px;
|
|
height: 60px;
|
|
border-radius: 50%;
|
|
margin: 0 auto 15px;
|
|
display: block;
|
|
object-fit: cover;
|
|
border: 3px solid white;
|
|
}
|
|
|
|
.token-name {
|
|
font-size: 1.5rem;
|
|
font-weight: 700;
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.token-symbol {
|
|
font-size: 1rem;
|
|
opacity: 0.9;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.token-description {
|
|
font-size: 0.9rem;
|
|
opacity: 0.8;
|
|
}
|
|
|
|
.upload-area {
|
|
border: 2px dashed #e9ecef;
|
|
border-radius: 8px;
|
|
padding: 30px;
|
|
text-align: center;
|
|
transition: border-color 0.2s ease;
|
|
}
|
|
|
|
.upload-area:hover {
|
|
border-color: #667eea;
|
|
}
|
|
|
|
.upload-area.dragover {
|
|
border-color: #667eea;
|
|
background: #f8f9ff;
|
|
}
|
|
|
|
.file-input {
|
|
display: none;
|
|
}
|
|
|
|
.current-icon {
|
|
max-width: 100px;
|
|
max-height: 100px;
|
|
border-radius: 8px;
|
|
margin: 10px 0;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.form-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.header-content {
|
|
flex-direction: column;
|
|
gap: 15px;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="header">
|
|
<div class="header-content">
|
|
<h1>🪙 Token Customization</h1>
|
|
<div>
|
|
<a href="dashboard.php" class="btn btn-secondary">← Back to Dashboard</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="container">
|
|
<?php if ($error_message): ?>
|
|
<div class="alert alert-error">
|
|
<?php echo htmlspecialchars($error_message); ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($success_message): ?>
|
|
<div class="alert alert-success">
|
|
<?php echo htmlspecialchars($success_message); ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- Token Preview -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
👁️ Live Preview
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="token-preview" style="--token-primary: <?php echo $token_settings['token_color_primary']; ?>; --token-secondary: <?php echo $token_settings['token_color_secondary']; ?>">
|
|
<img src="<?php echo htmlspecialchars($token_settings['token_icon']); ?>" alt="Token Icon" class="token-icon" onerror="this.src='/f_templates/tpl_frontend/img/default-token.png'">
|
|
<div class="token-name"><?php echo htmlspecialchars($token_settings['token_name']); ?></div>
|
|
<div class="token-symbol"><?php echo htmlspecialchars($token_settings['token_symbol']); ?></div>
|
|
<div class="token-description"><?php echo htmlspecialchars($token_settings['token_description']); ?></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Token Settings -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
⚙️ Token Settings
|
|
</div>
|
|
<div class="card-body">
|
|
<form method="POST" id="token-settings-form">
|
|
<input type="hidden" name="action" value="update_token_settings">
|
|
|
|
<div class="form-grid">
|
|
<div class="form-group">
|
|
<label class="form-label" for="token_name">Token Name</label>
|
|
<input type="text" id="token_name" name="token_name" class="form-input"
|
|
value="<?php echo htmlspecialchars($token_settings['token_name']); ?>"
|
|
placeholder="e.g., EasyCoins" required>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label" for="token_symbol">Token Symbol</label>
|
|
<input type="text" id="token_symbol" name="token_symbol" class="form-input"
|
|
value="<?php echo htmlspecialchars($token_settings['token_symbol']); ?>"
|
|
placeholder="e.g., EC" maxlength="5" required>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label" for="token_plural">Plural Form</label>
|
|
<input type="text" id="token_plural" name="token_plural" class="form-input"
|
|
value="<?php echo htmlspecialchars($token_settings['token_plural']); ?>"
|
|
placeholder="e.g., EasyCoins" required>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label" for="exchange_rate">Exchange Rate (USD)</label>
|
|
<input type="number" id="exchange_rate" name="exchange_rate" class="form-input"
|
|
value="<?php echo $token_settings['exchange_rate']; ?>"
|
|
step="0.01" min="0.01" placeholder="1.00" required>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label" for="min_purchase">Minimum Purchase</label>
|
|
<input type="number" id="min_purchase" name="min_purchase" class="form-input"
|
|
value="<?php echo $token_settings['min_purchase']; ?>"
|
|
step="0.01" min="0.01" placeholder="1.00" required>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label" for="max_purchase">Maximum Purchase</label>
|
|
<input type="number" id="max_purchase" name="max_purchase" class="form-input"
|
|
value="<?php echo $token_settings['max_purchase']; ?>"
|
|
step="0.01" min="1" placeholder="1000.00" required>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label" for="token_description">Description</label>
|
|
<textarea id="token_description" name="token_description" class="form-input" rows="3"
|
|
placeholder="Brief description of your platform currency"><?php echo htmlspecialchars($token_settings['token_description']); ?></textarea>
|
|
</div>
|
|
|
|
<div class="form-grid">
|
|
<div class="form-group">
|
|
<label class="form-label" for="token_color_primary">Primary Color</label>
|
|
<input type="color" id="token_color_primary" name="token_color_primary" class="color-input"
|
|
value="<?php echo $token_settings['token_color_primary']; ?>">
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label" for="token_color_secondary">Secondary Color</label>
|
|
<input type="color" id="token_color_secondary" name="token_color_secondary" class="color-input"
|
|
value="<?php echo $token_settings['token_color_secondary']; ?>">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>
|
|
<input type="checkbox" name="enabled" <?php echo $token_settings['enabled'] ? 'checked' : ''; ?>>
|
|
Enable token system
|
|
</label>
|
|
</div>
|
|
|
|
<button type="submit" class="btn">💾 Save Settings</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Token Icon Upload -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
🖼️ Token Icon
|
|
</div>
|
|
<div class="card-body">
|
|
<form method="POST" enctype="multipart/form-data" id="icon-upload-form">
|
|
<input type="hidden" name="action" value="upload_token_icon">
|
|
|
|
<div class="form-group">
|
|
<label class="form-label">Current Icon</label>
|
|
<div>
|
|
<img src="<?php echo htmlspecialchars($token_settings['token_icon']); ?>"
|
|
alt="Current Token Icon" class="current-icon"
|
|
onerror="this.src='/f_templates/tpl_frontend/img/default-token.png'">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="upload-area" onclick="document.getElementById('token_icon').click()">
|
|
<div style="font-size: 3rem; margin-bottom: 15px;">📁</div>
|
|
<div style="font-size: 1.1rem; margin-bottom: 10px;">Click to upload new token icon</div>
|
|
<div style="color: #666; font-size: 0.9rem;">
|
|
Supports PNG, JPG, GIF, SVG • Max 2MB • Recommended: 256x256px
|
|
</div>
|
|
<input type="file" id="token_icon" name="token_icon" class="file-input"
|
|
accept="image/png,image/jpeg,image/gif,image/svg+xml"
|
|
onchange="handleFileSelect(this)">
|
|
</div>
|
|
|
|
<div style="margin-top: 20px;">
|
|
<button type="submit" class="btn">📤 Upload Icon</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Reset Options -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
🔄 Reset Options
|
|
</div>
|
|
<div class="card-body">
|
|
<p style="margin-bottom: 20px; color: #666;">
|
|
Reset all token settings to default values. This action cannot be undone.
|
|
</p>
|
|
|
|
<form method="POST" onsubmit="return confirm('Are you sure you want to reset all token settings to defaults?')">
|
|
<input type="hidden" name="action" value="reset_to_defaults">
|
|
<button type="submit" class="btn btn-danger">🔄 Reset to Defaults</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Live preview updates
|
|
function updatePreview() {
|
|
const preview = document.querySelector('.token-preview');
|
|
const tokenName = document.getElementById('token_name').value;
|
|
const tokenSymbol = document.getElementById('token_symbol').value;
|
|
const tokenDescription = document.getElementById('token_description').value;
|
|
const primaryColor = document.getElementById('token_color_primary').value;
|
|
const secondaryColor = document.getElementById('token_color_secondary').value;
|
|
|
|
preview.style.setProperty('--token-primary', primaryColor);
|
|
preview.style.setProperty('--token-secondary', secondaryColor);
|
|
|
|
preview.querySelector('.token-name').textContent = tokenName || 'Token Name';
|
|
preview.querySelector('.token-symbol').textContent = tokenSymbol || 'SYM';
|
|
preview.querySelector('.token-description').textContent = tokenDescription || 'Token description';
|
|
}
|
|
|
|
// Add event listeners for live preview
|
|
document.getElementById('token_name').addEventListener('input', updatePreview);
|
|
document.getElementById('token_symbol').addEventListener('input', updatePreview);
|
|
document.getElementById('token_description').addEventListener('input', updatePreview);
|
|
document.getElementById('token_color_primary').addEventListener('change', updatePreview);
|
|
document.getElementById('token_color_secondary').addEventListener('change', updatePreview);
|
|
|
|
// File upload handling
|
|
function handleFileSelect(input) {
|
|
if (input.files && input.files[0]) {
|
|
const file = input.files[0];
|
|
const reader = new FileReader();
|
|
|
|
reader.onload = function(e) {
|
|
document.querySelector('.current-icon').src = e.target.result;
|
|
document.querySelector('.token-icon').src = e.target.result;
|
|
};
|
|
|
|
reader.readAsDataURL(file);
|
|
}
|
|
}
|
|
|
|
// Drag and drop for file upload
|
|
const uploadArea = document.querySelector('.upload-area');
|
|
|
|
uploadArea.addEventListener('dragover', function(e) {
|
|
e.preventDefault();
|
|
this.classList.add('dragover');
|
|
});
|
|
|
|
uploadArea.addEventListener('dragleave', function(e) {
|
|
e.preventDefault();
|
|
this.classList.remove('dragover');
|
|
});
|
|
|
|
uploadArea.addEventListener('drop', function(e) {
|
|
e.preventDefault();
|
|
this.classList.remove('dragover');
|
|
|
|
const files = e.dataTransfer.files;
|
|
if (files.length > 0) {
|
|
document.getElementById('token_icon').files = files;
|
|
handleFileSelect(document.getElementById('token_icon'));
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|