- Added software.php for managing software versions, including download and purchase actions. - Created upgrade_paths.php for handling upgrade paths management. - Developed user_licenses.php for managing user licenses. - Introduced version_access_rules.php for managing access rules for software versions. - Implemented frontend functions in functions.js for interacting with the software upgrade API. - Added version_access.php for user access validation and license management. - Created upgrades.php for displaying available upgrades and handling user interactions. - Enhanced UI with responsive design and progress indicators for downloads and purchases.
350 lines
12 KiB
JavaScript
350 lines
12 KiB
JavaScript
// Software Upgrade System - Frontend Functions
|
|
// Requires: jQuery or modern fetch API
|
|
|
|
class UpgradeManager {
|
|
constructor(apiBase = '/api.php') {
|
|
this.apiBase = apiBase;
|
|
this.serviceToken = '';
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
// Get service token from DOM if available
|
|
const tokenElement = document.getElementById('servicetoken');
|
|
if (tokenElement) {
|
|
this.serviceToken = tokenElement.innerHTML || '';
|
|
}
|
|
}
|
|
|
|
async makeAPICall(endpoint, method = 'GET', data = null) {
|
|
const url = this.apiBase + endpoint;
|
|
const bearer = 'Bearer ' + this.serviceToken;
|
|
|
|
const options = {
|
|
method: method,
|
|
headers: {
|
|
'Authorization': bearer,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
credentials: 'include'
|
|
};
|
|
|
|
if (data && (method === 'POST' || method === 'PUT')) {
|
|
options.body = JSON.stringify(data);
|
|
}
|
|
|
|
const response = await fetch(url, options);
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({ error: 'Network error' }));
|
|
throw new Error(errorData.error || `HTTP ${response.status}`);
|
|
}
|
|
|
|
return await response.json();
|
|
}
|
|
|
|
async getAvailableVersions() {
|
|
try {
|
|
const data = await this.makeAPICall('/v2/get/software?available');
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error fetching available versions:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async downloadVersion(versionId, onProgress = null) {
|
|
try {
|
|
// Step 1: Request download token
|
|
const downloadRequest = await this.makeAPICall('/v2/post/software', 'POST', {
|
|
action: 'download',
|
|
version_id: parseInt(versionId)
|
|
});
|
|
|
|
if (!downloadRequest.download_url) {
|
|
throw new Error('No download URL received');
|
|
}
|
|
|
|
// Step 2: Download file using temporary URL
|
|
await this.downloadFile(downloadRequest.download_url, onProgress);
|
|
|
|
} catch (error) {
|
|
console.error('Download error:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async downloadFile(url, onProgress) {
|
|
const response = await fetch(url, {
|
|
credentials: 'include'
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Download failed');
|
|
}
|
|
|
|
const contentLength = response.headers.get('Content-Length');
|
|
const total = parseInt(contentLength, 10);
|
|
let loaded = 0;
|
|
|
|
const reader = response.body.getReader();
|
|
const chunks = [];
|
|
|
|
while (true) {
|
|
const { done, value } = await reader.read();
|
|
|
|
if (done) break;
|
|
|
|
chunks.push(value);
|
|
loaded += value.length;
|
|
|
|
if (onProgress && total) {
|
|
onProgress(loaded, total);
|
|
}
|
|
}
|
|
|
|
// Create blob from chunks
|
|
const blob = new Blob(chunks);
|
|
|
|
// Trigger download
|
|
const downloadUrl = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = downloadUrl;
|
|
a.download = 'software_upgrade.zip'; // Filename will be set by server
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
window.URL.revokeObjectURL(downloadUrl);
|
|
document.body.removeChild(a);
|
|
}
|
|
|
|
async purchaseVersion(versionId, transactionId = null) {
|
|
try {
|
|
const purchaseData = {
|
|
action: 'purchase',
|
|
version_id: parseInt(versionId)
|
|
};
|
|
|
|
if (transactionId) {
|
|
purchaseData.transaction_id = transactionId;
|
|
}
|
|
|
|
const result = await this.makeAPICall('/v2/post/software', 'POST', purchaseData);
|
|
return result;
|
|
} catch (error) {
|
|
console.error('Purchase error:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
formatBytes(bytes) {
|
|
if (bytes === 0) return '0 Bytes';
|
|
const k = 1024;
|
|
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
|
|
}
|
|
|
|
formatPrice(price, currency = 'USD') {
|
|
return new Intl.NumberFormat('en-US', {
|
|
style: 'currency',
|
|
currency: currency
|
|
}).format(price);
|
|
}
|
|
}
|
|
|
|
// Global upgrade manager instance
|
|
let upgradeManager;
|
|
|
|
// Initialize upgrade system
|
|
function initUpgradeSystem() {
|
|
upgradeManager = new UpgradeManager();
|
|
}
|
|
|
|
// Display upgrade options in UI
|
|
async function showUpgradeOptions(containerId = 'upgrade-container') {
|
|
const container = document.getElementById(containerId);
|
|
if (!container) {
|
|
console.error('Container element not found:', containerId);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const data = await upgradeManager.getAvailableVersions();
|
|
|
|
container.innerHTML = '';
|
|
|
|
// Show current version info
|
|
if (data.current_version) {
|
|
const currentDiv = document.createElement('div');
|
|
currentDiv.className = 'current-version-info';
|
|
currentDiv.innerHTML = `
|
|
<h3>Your Current Version: ${data.current_version}</h3>
|
|
<p>Owned versions: ${data.owned_versions.map(v => v.version).join(', ')}</p>
|
|
`;
|
|
container.appendChild(currentDiv);
|
|
}
|
|
|
|
// Show available versions
|
|
if (data.available_versions && data.available_versions.length > 0) {
|
|
const versionsDiv = document.createElement('div');
|
|
versionsDiv.className = 'available-versions';
|
|
|
|
data.available_versions.forEach(version => {
|
|
const versionCard = document.createElement('div');
|
|
versionCard.className = 'version-card';
|
|
versionCard.dataset.versionId = version.id;
|
|
|
|
let buttonHTML = '';
|
|
let priceHTML = '';
|
|
let statusHTML = '';
|
|
|
|
if (version.is_accessible) {
|
|
statusHTML = '<span class="badge owned">Owned</span>';
|
|
buttonHTML = `<button onclick="downloadVersion(${version.id})" class="download-btn">Download</button>`;
|
|
} else if (version.requires_payment) {
|
|
if (version.is_upgrade) {
|
|
priceHTML = `
|
|
<div class="price-info">
|
|
<span class="upgrade-price">${upgradeManager.formatPrice(version.price)}</span>
|
|
<span class="original-price">${upgradeManager.formatPrice(version.original_price)}</span>
|
|
<span class="upgrade-label">Upgrade from v${version.upgrade_from}</span>
|
|
</div>
|
|
`;
|
|
} else {
|
|
priceHTML = `<div class="price-info">${upgradeManager.formatPrice(version.price)}</div>`;
|
|
}
|
|
buttonHTML = `<button onclick="purchaseVersion(${version.id}, ${version.price})" class="purchase-btn">Purchase</button>`;
|
|
} else if (version.access_reason === 'requires_base_version') {
|
|
statusHTML = `<span class="badge locked">Requires v${version.required_version}</span>`;
|
|
buttonHTML = `<button disabled class="locked-btn">Requires v${version.required_version}</button>`;
|
|
}
|
|
|
|
versionCard.innerHTML = `
|
|
<div class="version-header">
|
|
<h4>${version.name} ${statusHTML}</h4>
|
|
<span class="version-number">v${version.version}</span>
|
|
</div>
|
|
<div class="version-description">${version.description}</div>
|
|
<div class="version-meta">
|
|
<span class="file-size">Size: ${upgradeManager.formatBytes(version.file_size)}</span>
|
|
<span class="release-date">Released: ${new Date(version.release_date).toLocaleDateString()}</span>
|
|
</div>
|
|
${priceHTML}
|
|
<div class="version-actions">
|
|
${buttonHTML}
|
|
</div>
|
|
`;
|
|
|
|
versionsDiv.appendChild(versionCard);
|
|
});
|
|
|
|
container.appendChild(versionsDiv);
|
|
} else {
|
|
container.innerHTML = '<p>No software versions available at this time.</p>';
|
|
}
|
|
|
|
} catch (error) {
|
|
container.innerHTML = `<div class="error-message">Error loading upgrades: ${error.message}</div>`;
|
|
console.error('Error showing upgrade options:', error);
|
|
}
|
|
}
|
|
|
|
// Download version with progress
|
|
async function downloadVersion(versionId) {
|
|
const button = event.target;
|
|
const originalText = button.innerHTML;
|
|
|
|
try {
|
|
button.disabled = true;
|
|
button.innerHTML = 'Preparing Download...';
|
|
|
|
// Create progress indicator
|
|
const progressContainer = document.createElement('div');
|
|
progressContainer.className = 'download-progress';
|
|
progressContainer.innerHTML = `
|
|
<div class="progress-bar">
|
|
<div class="progress-fill" style="width: 0%"></div>
|
|
</div>
|
|
<div class="progress-text">0%</div>
|
|
`;
|
|
|
|
button.parentNode.appendChild(progressContainer);
|
|
|
|
const progressFill = progressContainer.querySelector('.progress-fill');
|
|
const progressText = progressContainer.querySelector('.progress-text');
|
|
|
|
await upgradeManager.downloadVersion(versionId, (loaded, total) => {
|
|
const percent = Math.round((loaded / total) * 100);
|
|
progressFill.style.width = percent + '%';
|
|
progressText.textContent = percent + '%';
|
|
});
|
|
|
|
button.innerHTML = 'Download Complete!';
|
|
progressText.textContent = 'Complete';
|
|
|
|
// Remove progress after a delay
|
|
setTimeout(() => {
|
|
progressContainer.remove();
|
|
button.innerHTML = originalText;
|
|
button.disabled = false;
|
|
}, 3000);
|
|
|
|
} catch (error) {
|
|
button.innerHTML = 'Download Failed';
|
|
button.disabled = false;
|
|
alert('Download failed: ' + error.message);
|
|
|
|
// Remove progress on error
|
|
const progressContainer = button.parentNode.querySelector('.download-progress');
|
|
if (progressContainer) {
|
|
progressContainer.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Purchase version
|
|
async function purchaseVersion(versionId, price) {
|
|
const button = event.target;
|
|
const originalText = button.innerHTML;
|
|
|
|
const confirmed = confirm(`Purchase this software version for ${upgradeManager.formatPrice(price)}?`);
|
|
if (!confirmed) return;
|
|
|
|
try {
|
|
button.disabled = true;
|
|
button.innerHTML = 'Processing Purchase...';
|
|
|
|
// Here you would integrate with your payment processor
|
|
// For now, we'll simulate with a transaction ID
|
|
const transactionId = 'txn_' + Date.now();
|
|
|
|
const result = await upgradeManager.purchaseVersion(versionId, transactionId);
|
|
|
|
if (result.success) {
|
|
button.innerHTML = 'Purchase Successful!';
|
|
button.className = 'success-btn';
|
|
|
|
// Refresh the upgrade options
|
|
setTimeout(() => {
|
|
showUpgradeOptions();
|
|
}, 2000);
|
|
} else {
|
|
throw new Error(result.error || 'Purchase failed');
|
|
}
|
|
|
|
} catch (error) {
|
|
button.innerHTML = 'Purchase Failed';
|
|
button.disabled = false;
|
|
alert('Purchase failed: ' + error.message);
|
|
}
|
|
}
|
|
|
|
// Initialize when DOM is ready
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
initUpgradeSystem();
|
|
});
|
|
|
|
// Export for module usage (optional)
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = { UpgradeManager, upgradeManager };
|
|
} |