diff --git a/CHANGELOG.md b/CHANGELOG.md index d5dc109..2182066 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## changes 11/18/2025 (v1.9.10) + +release(v1.9.10): add Pro bundle installer and admin panel polish + +- Add FileRise Pro section in admin panel with license management and bundle upload +- Persist Pro bundle under users/pro and sync public/api/pro endpoints on container startup +- Improve admin config API: Pro metadata, license file handling, hardened auth/CSRF helpers +- Update Pro badge/version UI with “update available” hint and link to filerise.net +- Change Pro bundle installer to always overwrite existing bundle files for clean upgrades + +--- + ## Changes 11/16/2025 (v1.9.9) release(v1.9.9): fix(branding): sanitize custom logo URL preview diff --git a/config/config.php b/config/config.php index 98a0377..afabdf9 100644 --- a/config/config.php +++ b/config/config.php @@ -240,30 +240,57 @@ if (strpos(BASE_URL, 'yourwebsite') !== false) { // Final: env var wins, else fallback define('SHARE_URL', getenv('SHARE_URL') ?: $defaultShare); -// -------------------------------- -// FileRise Pro (optional add-on) -// -------------------------------- +// ------------------------------------------------------------ +// FileRise Pro bootstrap wiring +// ------------------------------------------------------------ -// Where the Pro license JSON lives +// Inline license (optional; usually set via Admin UI and PRO_LICENSE_FILE) +if (!defined('FR_PRO_LICENSE')) { + $envLicense = getenv('FR_PRO_LICENSE'); + define('FR_PRO_LICENSE', $envLicense !== false ? trim((string)$envLicense) : ''); +} + +// JSON license file used by AdminController::setLicense() if (!defined('PRO_LICENSE_FILE')) { define('PRO_LICENSE_FILE', PROJECT_ROOT . '/users/proLicense.json'); } -// Inline/env license strings (optional) -if (!defined('FR_PRO_LICENSE')) { - define('FR_PRO_LICENSE', getenv('FR_PRO_LICENSE') ?: ''); -} +// Optional plain-text license file (used as fallback in bootstrap) if (!defined('FR_PRO_LICENSE_FILE')) { - define('FR_PRO_LICENSE_FILE', getenv('FR_PRO_LICENSE_FILE') ?: ''); + $lf = getenv('FR_PRO_LICENSE_FILE'); + if ($lf === false || $lf === '') { + $lf = PROJECT_ROOT . '/users/proLicense.txt'; + } + define('FR_PRO_LICENSE_FILE', $lf); } -// Optional Pro bootstrap (shipped only with Pro bundle) -$proBootstrap = PROJECT_ROOT . '/src/pro/bootstrap_pro.php'; -if (is_file($proBootstrap)) { +// Where Pro code lives by default → inside users volume +$proDir = getenv('FR_PRO_BUNDLE_DIR'); +if ($proDir === false || $proDir === '') { + $proDir = PROJECT_ROOT . '/users/pro'; +} +$proDir = rtrim($proDir, "/\\"); +if (!defined('FR_PRO_BUNDLE_DIR')) { + define('FR_PRO_BUNDLE_DIR', $proDir); +} + +// Try to load Pro bootstrap if enabled + present +$proBootstrap = FR_PRO_BUNDLE_DIR . '/bootstrap_pro.php'; +if (@is_file($proBootstrap)) { require_once $proBootstrap; } -// Safe default so the rest of the app always has the constant +// If bootstrap didn’t define these, give safe defaults if (!defined('FR_PRO_ACTIVE')) { define('FR_PRO_ACTIVE', false); } +if (!defined('FR_PRO_INFO')) { + define('FR_PRO_INFO', [ + 'valid' => false, + 'error' => null, + 'payload' => null, + ]); +} +if (!defined('FR_PRO_BUNDLE_VERSION')) { + define('FR_PRO_BUNDLE_VERSION', null); +} \ No newline at end of file diff --git a/public/api/admin/installProBundle.php b/public/api/admin/installProBundle.php new file mode 100644 index 0000000..1d4fd09 --- /dev/null +++ b/public/api/admin/installProBundle.php @@ -0,0 +1,8 @@ +installProBundle(); \ No newline at end of file diff --git a/public/js/adminPanel.js b/public/js/adminPanel.js index 8085953..7175757 100644 --- a/public/js/adminPanel.js +++ b/public/js/adminPanel.js @@ -14,6 +14,9 @@ function normalizeLogoPath(raw) { } const version = window.APP_VERSION || "dev"; +// Hard-coded *FOR NOW* latest FileRise Pro bundle version for UI hints only. +// Update this when I cut a new Pro ZIP. +const PRO_LATEST_BUNDLE_VERSION = 'v1.0.0'; function getAdminTitle(isPro, proVersion) { const corePill = ` @@ -22,6 +25,23 @@ function getAdminTitle(isPro, proVersion) { `; + // Normalize versions so "v1.0.1" and "1.0.1" compare cleanly + const norm = (v) => String(v || '').trim().replace(/^v/i, ''); + + const latestRaw = (typeof PRO_LATEST_BUNDLE_VERSION !== 'undefined' + ? PRO_LATEST_BUNDLE_VERSION + : '' + ); + + const currentRaw = (proVersion && proVersion !== 'not installed') + ? String(proVersion) + : ''; + + const hasCurrent = !!norm(currentRaw); + const hasLatest = !!norm(latestRaw); + const hasUpdate = isPro && hasCurrent && hasLatest && + norm(currentRaw) !== norm(latestRaw); + if (!isPro) { // Free/core only return ` @@ -30,18 +50,32 @@ function getAdminTitle(isPro, proVersion) { `; } - const pv = proVersion ? `Pro v${proVersion}` : 'Pro'; + const pvLabel = hasCurrent ? `Pro v${norm(currentRaw)}` : 'Pro'; const proPill = ` - ${pv} + ${pvLabel} `; + const updateHint = hasUpdate + ? ` + + Pro update available + + ` + : ''; + return ` ${t("admin_panel")} ${corePill} ${proPill} + ${updateHint} `; } @@ -450,6 +484,81 @@ function toggleSection(id) { } } +export function initProBundleInstaller() { + try { + const fileInput = document.getElementById('proBundleFile'); + const btn = document.getElementById('btnInstallProBundle'); + const statusEl = document.getElementById('proBundleStatus'); + + if (!fileInput || !btn || !statusEl) return; + + // Allow names like: FileRisePro_v1.0.0.zip or FileRisePro-1.0.0.zip + const PRO_ZIP_NAME_RE = /^FileRisePro[_-]v?[0-9]+\.[0-9]+\.[0-9]+\.zip$/i; + + btn.addEventListener('click', async () => { + const file = fileInput.files && fileInput.files[0]; + + if (!file) { + statusEl.textContent = 'Choose a FileRise Pro .zip bundle first.'; + statusEl.className = 'small text-danger'; + return; + } + + const name = file.name || ''; + if (!PRO_ZIP_NAME_RE.test(name)) { + statusEl.textContent = 'Bundle must be named like "FileRisePro_v1.0.0.zip".'; + statusEl.className = 'small text-danger'; + return; + } + + const formData = new FormData(); + formData.append('bundle', file); + + statusEl.textContent = 'Uploading and installing Pro bundle...'; + statusEl.className = 'small text-muted'; + + try { + const resp = await fetch('/api/admin/installProBundle.php', { + method: 'POST', + headers: { + 'X-CSRF-Token': window.csrfToken || '' + }, + body: formData + }); + + let data = null; + try { + data = await resp.json(); + } catch (_) { + // ignore JSON parse errors; handled below + } + + if (!resp.ok || !data || !data.success) { + const msg = data && data.error + ? data.error + : `HTTP ${resp.status}`; + statusEl.textContent = 'Install failed: ' + msg; + statusEl.className = 'small text-danger'; + return; + } + + const versionText = data.proVersion ? ` (version ${data.proVersion})` : ''; + statusEl.textContent = 'Pro bundle installed' + versionText + '. Reload the page to apply changes.'; + statusEl.className = 'small text-success'; + + if (typeof loadAdminConfigFunc === 'function') { + loadAdminConfigFunc(); + } + } catch (e) { + statusEl.textContent = 'Install failed: ' + (e && e.message ? e.message : String(e)); + statusEl.className = 'small text-danger'; + } + }); + } catch (e) { + console.warn('Failed to init Pro bundle installer', e); + } +} + function loadShareLinksSection() { const container = document.getElementById("shareLinksContent"); if (!container) return; @@ -1266,18 +1375,32 @@ async function ooProbeFrame(docsOrigin, timeoutMs = 4000) { // --- FileRise Pro / License section --- const proContent = document.getElementById("proContent"); if (proContent) { + // Normalize versions so "v1.0.1" and "1.0.1" compare cleanly + const norm = (v) => (String(v || '').trim().replace(/^v/i, '')); + + const currentVersionRaw = (proVersion && proVersion !== 'not installed') ? String(proVersion) : ''; + const latestVersionRaw = PRO_LATEST_BUNDLE_VERSION || ''; + const hasCurrent = !!norm(currentVersionRaw); + const hasLatest = !!norm(latestVersionRaw); + const hasUpdate = hasCurrent && hasLatest && norm(currentVersionRaw) !== norm(latestVersionRaw); + const proMetaHtml = isPro && (proType || proEmail || proVersion) ? ` -
+
✅ ${proType ? `License type: ${proType}` : 'License active'} ${proType && proEmail ? ' • ' : ''} ${proEmail ? `Licensed to: ${proEmail}` : ''}
+ ${hasCurrent ? `
- Pro bundle version: v${proVersion} -
+ Installed Pro bundle: v${norm(currentVersionRaw)} +
` : ''} + ${hasLatest ? ` +
+ Latest Pro bundle (UI hint): ${latestVersionRaw} +
` : ''}
` : ''; @@ -1308,8 +1431,13 @@ if (proContent) { href="https://filerise.net/pro/update.php" target="_blank" rel="noopener noreferrer" - class="btn btn-sm btn-pro-admin"> - Download latest Pro bundle + class="btn btn-sm btn-pro-admin d-inline-flex align-items-center" + > + Download latest Pro bundle + ${hasUpdate ? ` + + Update available + ` : ''} Opens filerise.net in a new tab where you can enter your Pro license @@ -1322,7 +1450,8 @@ if (proContent) { href="https://filerise.net/pro/checkout.php" target="_blank" rel="noopener noreferrer" - class="btn btn-sm btn-pro-admin"> + class="btn btn-sm btn-pro-admin" + > Buy FileRise Pro @@ -1332,7 +1461,16 @@ if (proContent) { `}
- +
+ + ${isPro && proLicense ? ` + + ` : ''} +