- 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
370 lines
12 KiB
PHP
370 lines
12 KiB
PHP
<?php
|
|
/*******************************************************************************************************************
|
|
| EasyStream Token System Class
|
|
| Handles customizable platform tokens for monetization
|
|
|*******************************************************************************************************************/
|
|
|
|
defined('_ISVALID') or header('Location: /error');
|
|
|
|
class VToken {
|
|
private static $settings = null;
|
|
private static $db;
|
|
private static $cfg;
|
|
|
|
public function __construct() {
|
|
global $class_database, $cfg;
|
|
self::$db = $class_database;
|
|
self::$cfg = $cfg;
|
|
}
|
|
|
|
/**
|
|
* Get token settings from database
|
|
*/
|
|
public static function getSettings() {
|
|
if (self::$settings !== null) {
|
|
return self::$settings;
|
|
}
|
|
|
|
global $class_database;
|
|
|
|
// Default settings
|
|
$defaults = [
|
|
'name' => 'EasyCoins',
|
|
'symbol' => 'EC',
|
|
'plural' => 'EasyCoins',
|
|
'description' => 'Platform currency for tips and donations',
|
|
'icon' => '/f_templates/tpl_frontend/img/default-token.png',
|
|
'color_primary' => '#FFD700',
|
|
'color_secondary' => '#FFA500',
|
|
'exchange_rate' => 1.00,
|
|
'min_purchase' => 1.00,
|
|
'max_purchase' => 1000.00,
|
|
'enabled' => true
|
|
];
|
|
|
|
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']);
|
|
$value = $result->fields['setting_value'];
|
|
|
|
// Convert numeric values
|
|
if (in_array($key, ['exchange_rate', 'min_purchase', 'max_purchase'])) {
|
|
$value = floatval($value);
|
|
} elseif ($key === 'enabled') {
|
|
$value = $value === '1';
|
|
}
|
|
|
|
$settings[$key] = $value;
|
|
$result->MoveNext();
|
|
}
|
|
|
|
self::$settings = $settings;
|
|
return $settings;
|
|
|
|
} catch (Exception $e) {
|
|
self::$settings = $defaults;
|
|
return $defaults;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get token display HTML
|
|
*/
|
|
public static function getTokenDisplay($amount, $size = 'normal', $show_icon = true) {
|
|
$settings = self::getSettings();
|
|
|
|
if (!$settings['enabled']) {
|
|
return '$' . number_format($amount, 2);
|
|
}
|
|
|
|
$sizes = [
|
|
'small' => ['icon' => '16px', 'font' => '0.875rem'],
|
|
'normal' => ['icon' => '20px', 'font' => '1rem'],
|
|
'large' => ['icon' => '24px', 'font' => '1.25rem'],
|
|
'xlarge' => ['icon' => '32px', 'font' => '1.5rem']
|
|
];
|
|
|
|
$size_config = $sizes[$size] ?? $sizes['normal'];
|
|
|
|
$icon_html = '';
|
|
if ($show_icon) {
|
|
$icon_html = sprintf(
|
|
'<img src="%s" alt="%s" style="width: %s; height: %s; margin-right: 4px; vertical-align: middle; border-radius: 50%%;">',
|
|
htmlspecialchars($settings['icon']),
|
|
htmlspecialchars($settings['symbol']),
|
|
$size_config['icon'],
|
|
$size_config['icon']
|
|
);
|
|
}
|
|
|
|
$token_name = $amount == 1 ? $settings['name'] : $settings['plural'];
|
|
|
|
return sprintf(
|
|
'<span class="token-display" style="color: %s; font-size: %s; font-weight: 500; display: inline-flex; align-items: center;">%s%s %s</span>',
|
|
$settings['color_primary'],
|
|
$size_config['font'],
|
|
$icon_html,
|
|
number_format($amount, 0),
|
|
htmlspecialchars($token_name)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get token symbol only
|
|
*/
|
|
public static function getSymbol() {
|
|
$settings = self::getSettings();
|
|
return $settings['symbol'];
|
|
}
|
|
|
|
/**
|
|
* Get token name
|
|
*/
|
|
public static function getName($plural = false) {
|
|
$settings = self::getSettings();
|
|
return $plural ? $settings['plural'] : $settings['name'];
|
|
}
|
|
|
|
/**
|
|
* Get token icon HTML
|
|
*/
|
|
public static function getIcon($size = '20px') {
|
|
$settings = self::getSettings();
|
|
|
|
return sprintf(
|
|
'<img src="%s" alt="%s" style="width: %s; height: %s; border-radius: 50%%; object-fit: cover;">',
|
|
htmlspecialchars($settings['icon']),
|
|
htmlspecialchars($settings['symbol']),
|
|
$size,
|
|
$size
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Convert USD to tokens
|
|
*/
|
|
public static function usdToTokens($usd_amount) {
|
|
$settings = self::getSettings();
|
|
return round($usd_amount / $settings['exchange_rate']);
|
|
}
|
|
|
|
/**
|
|
* Convert tokens to USD
|
|
*/
|
|
public static function tokensToUsd($token_amount) {
|
|
$settings = self::getSettings();
|
|
return $token_amount * $settings['exchange_rate'];
|
|
}
|
|
|
|
/**
|
|
* Get purchase limits
|
|
*/
|
|
public static function getPurchaseLimits() {
|
|
$settings = self::getSettings();
|
|
return [
|
|
'min' => $settings['min_purchase'],
|
|
'max' => $settings['max_purchase'],
|
|
'min_tokens' => self::usdToTokens($settings['min_purchase']),
|
|
'max_tokens' => self::usdToTokens($settings['max_purchase'])
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Validate token amount
|
|
*/
|
|
public static function validateAmount($amount, $type = 'usd') {
|
|
$limits = self::getPurchaseLimits();
|
|
|
|
if ($type === 'tokens') {
|
|
$min = $limits['min_tokens'];
|
|
$max = $limits['max_tokens'];
|
|
} else {
|
|
$min = $limits['min'];
|
|
$max = $limits['max'];
|
|
}
|
|
|
|
if ($amount < $min) {
|
|
return ['valid' => false, 'message' => "Minimum amount is $min"];
|
|
}
|
|
|
|
if ($amount > $max) {
|
|
return ['valid' => false, 'message' => "Maximum amount is $max"];
|
|
}
|
|
|
|
return ['valid' => true];
|
|
}
|
|
|
|
/**
|
|
* Get token CSS for styling
|
|
*/
|
|
public static function getTokenCSS() {
|
|
$settings = self::getSettings();
|
|
|
|
return sprintf('
|
|
<style>
|
|
:root {
|
|
--token-primary: %s;
|
|
--token-secondary: %s;
|
|
}
|
|
.token-display {
|
|
color: var(--token-primary);
|
|
font-weight: 500;
|
|
}
|
|
.token-button {
|
|
background: linear-gradient(135deg, var(--token-primary), var(--token-secondary));
|
|
color: white;
|
|
border: none;
|
|
padding: 8px 16px;
|
|
border-radius: 20px;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
}
|
|
.token-button:hover {
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
|
|
}
|
|
.token-badge {
|
|
background: var(--token-primary);
|
|
color: white;
|
|
padding: 4px 8px;
|
|
border-radius: 12px;
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
}
|
|
</style>
|
|
', $settings['color_primary'], $settings['color_secondary']);
|
|
}
|
|
|
|
/**
|
|
* Get donation button HTML
|
|
*/
|
|
public static function getDonationButton($streamer_id, $suggested_amount = null) {
|
|
$settings = self::getSettings();
|
|
|
|
if (!$settings['enabled']) {
|
|
return '';
|
|
}
|
|
|
|
$suggested_tokens = $suggested_amount ? self::usdToTokens($suggested_amount) : 10;
|
|
|
|
return sprintf('
|
|
<button class="token-button" onclick="openTokenDonation(%d, %d)">
|
|
%s Donate %s %s
|
|
</button>
|
|
',
|
|
$streamer_id,
|
|
$suggested_tokens,
|
|
self::getIcon('16px'),
|
|
number_format($suggested_tokens),
|
|
$settings['name']
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get token purchase form HTML
|
|
*/
|
|
public static function getPurchaseForm($user_id) {
|
|
$settings = self::getSettings();
|
|
$limits = self::getPurchaseLimits();
|
|
|
|
if (!$settings['enabled']) {
|
|
return '<p>Token system is currently disabled.</p>';
|
|
}
|
|
|
|
ob_start();
|
|
?>
|
|
<div class="token-purchase-form">
|
|
<div class="token-header">
|
|
<h3><?php echo self::getIcon('32px'); ?> Purchase <?php echo htmlspecialchars($settings['plural']); ?></h3>
|
|
<p><?php echo htmlspecialchars($settings['description']); ?></p>
|
|
</div>
|
|
|
|
<div class="purchase-options">
|
|
<div class="amount-selector">
|
|
<label>Amount (USD)</label>
|
|
<input type="number" id="usd-amount" min="<?php echo $limits['min']; ?>" max="<?php echo $limits['max']; ?>" step="0.01" value="10.00">
|
|
</div>
|
|
|
|
<div class="token-preview">
|
|
<span>You will receive:</span>
|
|
<div class="token-amount" id="token-amount">
|
|
<?php echo self::getTokenDisplay(self::usdToTokens(10), 'large'); ?>
|
|
</div>
|
|
</div>
|
|
|
|
<button class="token-button" onclick="processPurchase()">
|
|
💳 Purchase <?php echo htmlspecialchars($settings['plural']); ?>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
document.getElementById('usd-amount').addEventListener('input', function() {
|
|
const usdAmount = parseFloat(this.value) || 0;
|
|
const tokenAmount = Math.round(usdAmount / <?php echo $settings['exchange_rate']; ?>);
|
|
document.getElementById('token-amount').innerHTML = '<?php echo self::getIcon('24px'); ?>' + tokenAmount + ' <?php echo htmlspecialchars($settings['plural']); ?>';
|
|
});
|
|
|
|
function processPurchase() {
|
|
const amount = document.getElementById('usd-amount').value;
|
|
window.location.href = '/donations/process?amount=' + amount + '&user_id=<?php echo $user_id; ?>';
|
|
}
|
|
</script>
|
|
<?php
|
|
return ob_get_clean();
|
|
}
|
|
|
|
/**
|
|
* Record token transaction
|
|
*/
|
|
public static function recordTransaction($user_id, $amount, $type, $description = '') {
|
|
global $class_database;
|
|
|
|
try {
|
|
$sql = "INSERT INTO token_transactions (user_id, amount, transaction_type, description, created_at)
|
|
VALUES (?, ?, ?, ?, NOW())";
|
|
|
|
$class_database->execute($sql, [$user_id, $amount, $type, $description]);
|
|
|
|
// Update user balance
|
|
$balance_sql = "UPDATE db_accountuser SET token_balance = COALESCE(token_balance, 0) + ? WHERE usr_id = ?";
|
|
$class_database->execute($balance_sql, [$amount, $user_id]);
|
|
|
|
return true;
|
|
|
|
} catch (Exception $e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get user token balance
|
|
*/
|
|
public static function getUserBalance($user_id) {
|
|
global $class_database;
|
|
|
|
try {
|
|
$sql = "SELECT COALESCE(token_balance, 0) as balance FROM db_accountuser WHERE usr_id = ?";
|
|
$result = $class_database->execute($sql, [$user_id]);
|
|
|
|
return $result->fields['balance'] ?? 0;
|
|
|
|
} catch (Exception $e) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if user can afford amount
|
|
*/
|
|
public static function canAfford($user_id, $amount) {
|
|
$balance = self::getUserBalance($user_id);
|
|
return $balance >= $amount;
|
|
}
|
|
}
|
|
?>
|