Code
FlatlyPage
A modern CMS for building landing pages.
Version history
Version 3.0.0.1 2026-09-26
Browse filesFiles changed23
Lines added+23
Lines removed-61
.htaccess
⋮ 17 unchanged lines ⋮ RewriteRule ^config\.php$ - [F,L] RewriteRule ^themes\.css$ - [F,L] - # Extensions were removed: block leftovers from older versions RewriteRule ^extensions(/|$) - [F,L] RewriteRule ^(extensions-loader|admin/extensions|admin/popups)(\.php)?$ - [G,L] ⋮ 95 unchanged lines ⋮
admin/ai-helper-lib.php
⋮ 235 unchanged lines ⋮ if ($index >= $currentCount) { $item = array_merge($itemSchema['base'], $item); } - // An unchanged item must stay a JSON object ({}), not [], or the editor would replace the whole list. $items[] = $item === [] ? new stdClass() : $item; } if ($items !== []) { ⋮ 724 unchanged lines ⋮
admin/dashboard.php
⋮ 1397 unchanged lines ⋮ el.classList.toggle('is-error', !ok); } - // Saves the consent settings together with the given script list; nothing else on the page is submitted. function persist(next, statusEl) { if (busy) return Promise.resolve(false); busy = true; ⋮ 999 unchanged lines ⋮
admin/login_tracking.php
⋮ 153 unchanged lines ⋮ public function updateActivity() { - if (!isset($_SESSION['login_token'])) { + if (!isset($_SESSION['login_token']) || !is_string($_SESSION['login_token']) || preg_match('/^[a-f0-9]{64}$/', $_SESSION['login_token']) !== 1) { return false; } ⋮ 64 unchanged lines ⋮ public function logoutSession($loginId) { + if (!is_string($loginId) || preg_match('/^login_[a-f0-9]+\.[0-9]+$/', $loginId) !== 1) { + return false; + } + $xml = new DOMDocument('1.0', 'UTF-8'); $xml->formatOutput = true; $xml->preserveWhiteSpace = false; ⋮ 25 unchanged lines ⋮ public function banIP($ip) { + if (!is_string($ip) || filter_var($ip, FILTER_VALIDATE_IP) === false) { + return false; + } + $bannedFile = DATA_DIR . '/private/banned_ips.json'; $banned = []; ⋮ 56 unchanged lines ⋮ private function logoutByIP($ip) { + if (!is_string($ip) || filter_var($ip, FILTER_VALIDATE_IP) === false) { + return false; + } + $xml = new DOMDocument('1.0', 'UTF-8'); $xml->formatOutput = true; $xml->preserveWhiteSpace = false; ⋮ 55 unchanged lines ⋮
admin/system.php
⋮ 74 unchanged lines ⋮ <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>System - FlatlyPage CMS</title> - <meta name="generator" content="FlatlyPage CMS"> <link rel="icon" href="admin.ico?v=<?= filemtime(__DIR__ . '/../css/admin.css') ?>" type="image/x-icon"> <?= admin_font_head() ?> <link rel="stylesheet" href="/css/admin.css?v=<?= filemtime(__DIR__ . '/../css/admin.css') ?>"> ⋮ 138 unchanged lines ⋮
admin/theme-edit/index.php
⋮ 5 unchanged lines ⋮ <!-- Powered by Renderion Engine --> <!DOCTYPE html> - <html lang="en" data-cms="flatlypage"> + <html lang="en"> <head> <meta charset="UTF-8"> ⋮ 2 unchanged lines ⋮ $domain = preg_replace('/[^a-z0-9\.\-]/i', '', $domain); $domain = ucfirst(strtolower($domain)); ?> <title>Create your own Theme</title> - <meta name="generator" content="FlatlyPage CMS"> <meta name="author" content="FlatlyPage Team"> <meta name="robots" content="noindex, nofollow"> ⋮ 112 unchanged lines ⋮
admin/themes.php
⋮ 277 unchanged lines ⋮ <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Themes - FlatlyPage CMS</title> - <meta name="generator" content="FlatlyPage CMS"> <link rel="icon" href="admin.ico?v=<?= filemtime(__DIR__ . '/../css/admin.css') ?>" type="image/x-icon"> <?= admin_font_head() ?> <link rel="stylesheet" href="/css/admin.css?v=<?= filemtime(__DIR__ . '/../css/admin.css') ?>"> ⋮ 170 unchanged lines ⋮
admin/updater-lib.php
Not shown (binary file or too large to diff).
admin/updater.php
⋮ 135 unchanged lines ⋮ <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Updater - FlatlyPage CMS</title> - <meta name="generator" content="FlatlyPage CMS"> <link rel="icon" href="admin.ico?v=<?= filemtime(__DIR__ . '/../css/admin.css') ?>" type="image/x-icon"> <?= admin_font_head() ?> <link rel="stylesheet" href="/css/admin.css?v=<?= filemtime(__DIR__ . '/../css/admin.css') ?>"> ⋮ 319 unchanged lines ⋮
api/analytics.php
⋮ 30 unchanged lines ⋮ analytics_respond(413, ['ok' => false, 'error' => 'The scripts are too large.']); } - // The settings arrive base64-encoded so hosting firewalls don't block a body containing <script>. $input = json_decode((string) file_get_contents('php://input', false, null, 0, $maxBody + 1), true); $decoded = is_array($input) && is_string($input['payload'] ?? null) ? base64_decode($input['payload'], true) : false; $data = $decoded !== false ? json_decode($decoded, true) : null; ⋮ 12 unchanged lines ⋮
assets/js/admin-editor.js
⋮ 538 unchanged lines ⋮ const settingsForm = document.getElementById('settingsForm'); if (settingsForm) { - // A field in a hidden tab can't show its validation message, so the form would silently not submit. settingsForm.addEventListener('invalid', (event) => { const panel = event.target.closest('.settings-panel'); if (panel && !panel.classList.contains('active')) activateSettingsTab(panel.id.replace(/^panel-/, '')); }, true); - // Code fields are sent base64-encoded so hosting firewalls don't block posts containing <script>. settingsForm.addEventListener('submit', () => { settingsForm.querySelectorAll('.flatly-encoded').forEach(input => input.remove()); settingsForm.querySelectorAll('textarea[data-encode]').forEach(area => { ⋮ 10 unchanged lines ⋮ }); }); - // Coming back to the page (bfcache) must give the fields their names back. window.addEventListener('pageshow', () => { settingsForm.querySelectorAll('.flatly-encoded').forEach(input => input.remove()); settingsForm.querySelectorAll('textarea[data-encode-name]').forEach(area => { area.name = area.dataset.encodeName; }); ⋮ 2204 unchanged lines ⋮
assets/js/consent.js
⋮ 24 unchanged lines ⋮ const script = document.createElement('script'); Array.from(old.attributes).forEach(attr => script.setAttribute(attr.name, attr.value)); script.text = old.textContent; - // External scripts without async run in order: the next one waits for this one to load. const waits = script.src && !old.hasAttribute('async') && !old.hasAttribute('defer'); if (waits) { script.async = false; ⋮ 46 unchanged lines ⋮ if (value === 'accepted') { activate(); } else if (previous === 'accepted' && activated) { - // Scripts that already ran cannot be unloaded, so reload without them. location.reload(); } } ⋮ 65 unchanged lines ⋮
config.php
⋮ 464 unchanged lines ⋮ const CUSTOM_CODE_MAX_LENGTH = 100000; - /** - * Reads a code field that the admin sends base64-encoded as "<field>_b64" (so hosting firewalls - * don't block posts containing <script>); falls back to the plain field. Works for arrays too. - */ function flatly_posted_code(string $field): mixed { $decode = static function (mixed $value): string { ⋮ 18 unchanged lines ⋮ return mb_substr(trim($code), 0, CUSTOM_CODE_MAX_LENGTH); } - /** - * Custom JavaScript from Settings > Custom, printed at the end of <body> on public pages. - * Plain code is wrapped in a <script> tag; a snippet that already has its own <script> tags - * (e.g. an analytics embed) is printed as-is. - */ function flatly_custom_js(): string { $code = (string) (get_system_settings()['custom_js'] ?? ''); ⋮ 15 unchanged lines ⋮ ]; const CUSTOM_HTML_MAX_SNIPPETS = 20; - /** Builds the Custom HTML list from the parallel custom_html_location[] / custom_html_code[] form fields. */ function flatly_clean_custom_html(mixed $locations, mixed $codes): array { $locations = is_array($locations) ? array_values($locations) : []; ⋮ 14 unchanged lines ⋮ return $snippets; } - /** Custom HTML from Settings > Custom (and analytics scripts) for one location on public pages, printed as-is. */ function flatly_custom_html(string $location): string { $out = flatly_analytics_html($location); ⋮ 79 unchanged lines ⋮ ]; } - /** - * Analytics scripts for one placement. With the consent manager on, scripts that require consent - * are printed inside an inert <template>; assets/js/consent.js runs them after the visitor accepts. - */ function flatly_analytics_html(string $location): string { $analytics = flatly_analytics_settings(); ⋮ 256 unchanged lines ⋮
contact.php
⋮ 1 unchanged line ⋮ error_reporting(E_ALL); ini_set('display_errors', 0); - header("X-Powered-By: FlatlyPage CMS"); + header_remove('X-Powered-By'); header("X-Content-Type-Options: nosniff"); header("X-Frame-Options: SAMEORIGIN"); header("X-XSS-Protection: 1; mode=block"); ⋮ 67 unchanged lines ⋮ } } ?> - <!-- Powered by FlatlyPage CMS --> <!DOCTYPE html> - <html lang="<?php echo htmlspecialchars($lang); ?>" data-cms="flatlypage"> + <html lang="<?php echo htmlspecialchars($lang); ?>"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title><?= e($page_title) ?> - <?= e($site_settings['site_name']) ?></title> <meta name="description" content="<?= e($page_description) ?>"> - <meta name="generator" content="FlatlyPage CMS"> <?= flatly_canonical_link() ?> <?= flatly_hreflang_links() ?> <?= flatly_language_detect_script() ?> ⋮ 336 unchanged lines ⋮
cron-update.php
<?php - /* - * FlatlyPage CMS - automatic updates, run by a cron job. - * - * php /path/to/site/cron-update.php - * - * or, when the host can only call a URL: - * - * https://example.com/cron-update?token=SECRET - * - * It does nothing unless both the updater and automatic updates are turned on in Admin > Updater, - * where the exact command, URL and token are shown. - */ - $isCli = PHP_SAPI === 'cli'; if ($isCli) { - // config.php expects a web request; without these it would guess the site path from the file system. $_SERVER['HTTP_HOST'] ??= 'localhost'; $_SERVER['SERVER_PORT'] ??= 80; $_SERVER['SCRIPT_NAME'] = '/cron-update.php'; ⋮ 32 unchanged lines ⋮
css/styles.css
Not shown (binary file or too large to diff).
engine/index.php
<!-- Powered by Renderion Engine --> <!DOCTYPE html> - <html lang="en" data-cms="flatlypage"> + <html lang="en"> <head> <meta charset="UTF-8"> ⋮ 3 unchanged lines ⋮ $domain = ucfirst(strtolower($domain)); ?> <title><?= htmlspecialchars($domain, ENT_QUOTES, 'UTF-8') ?>: Renderion Engine</title> <!-- Dynamically added --> <link rel="icon" href="renderion.ico?v=<?= filemtime(__DIR__ . '/../engine/renderion.ico') ?>" type="image/x-icon"> - <meta name="generator" content="FlatlyPage CMS"> <meta name="author" content="FlatlyPage Team"> <meta name="robots" content="noindex, nofollow"> ⋮ 110 unchanged lines ⋮
fonts/Orbit.ttf
index.php
⋮ 1 unchanged line ⋮ error_reporting(E_ALL); ini_set('display_errors', 0); - header("X-Powered-By: FlatlyPage CMS"); + header_remove('X-Powered-By'); header("X-Content-Type-Options: nosniff"); header("X-Frame-Options: SAMEORIGIN"); header("X-XSS-Protection: 1; mode=block"); ⋮ 50 unchanged lines ⋮ } } ?> - <!-- Powered by FlatlyPage CMS --> <!DOCTYPE html> - <html lang="<?php echo htmlspecialchars($lang); ?>" data-cms="flatlypage"> + <html lang="<?php echo htmlspecialchars($lang); ?>"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title><?= e($page_title) ?></title> <meta name="description" content="<?= e($page_description) ?>"> - <meta name="generator" content="FlatlyPage CMS"> <?= flatly_canonical_link() ?> <?= flatly_hreflang_links() ?> <?= flatly_language_detect_script() ?> ⋮ 314 unchanged lines ⋮
newsletter/manager.php
Not shown (binary file or too large to diff).
newsletter/newsletter-unavailable.php
<!-- Powered by Renderion Engine --> <!DOCTYPE html> - <html lang="en" data-cms="flatlypage"> + <html lang="en"> <head> <meta charset="UTF-8"> ⋮ 1 unchanged line ⋮ <?php $domain = $_SERVER['HTTP_HOST'] ?? 'localhost'; $domain = preg_replace('/[^a-z0-9\.\-]/i', '', $domain); $domain = ucfirst(strtolower($domain)); ?> <title><?= htmlspecialchars($domain, ENT_QUOTES, 'UTF-8') ?></title> <!-- Dynamically added --> <link rel="icon" href="nl.ico?v=<?= filemtime(__DIR__ . '/../newsletter/nl.ico') ?>" type="image/x-icon"> - <meta name="generator" content="FlatlyPage CMS"> <meta name="author" content="FlatlyPage Team"> <meta name="robots" content="noindex, nofollow"> ⋮ 110 unchanged lines ⋮
page.php
⋮ 1 unchanged line ⋮ error_reporting(E_ALL); ini_set('display_errors', 0); - header("X-Powered-By: FlatlyPage CMS"); + header_remove('X-Powered-By'); header("X-Content-Type-Options: nosniff"); header("X-Frame-Options: SAMEORIGIN"); header("X-XSS-Protection: 1; mode=block"); ⋮ 74 unchanged lines ⋮ } } ?> - <!-- Powered by FlatlyPage CMS --> <!DOCTYPE html> - <html lang="<?php echo htmlspecialchars($lang); ?>" data-cms="flatlypage"> + <html lang="<?php echo htmlspecialchars($lang); ?>"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title><?= e($page_title) ?> - <?= e($site_settings['site_name']) ?></title> <meta name="description" content="<?= e($page_description) ?>"> - <meta name="generator" content="FlatlyPage CMS"> <?= flatly_canonical_link() ?> <?= flatly_hreflang_links() ?> <?= flatly_language_detect_script() ?> ⋮ 253 unchanged lines ⋮
version.txt
- 3.0.0.0 + 3.0.0.1