WebOrbiton
v3.0.0.0

FlatlyPage

388 lines · 17.7 KB
  1. <?php
  2. error_reporting(E_ALL);
  3. ini_set('display_errors', 0);
  4. ​
  5. header("X-Powered-By: FlatlyPage CMS");
  6. header("X-Content-Type-Options: nosniff");
  7. header("X-Frame-Options: SAMEORIGIN");
  8. header("X-XSS-Protection: 1; mode=block");
  9. header("Referrer-Policy: strict-origin-when-cross-origin");
  10. ​
  11. require_once __DIR__ . '/config.php';
  12. require_once __DIR__ . '/admin/publish-helpers.php';
  13. ​
  14. if (!flatly_init_front_lang()) {
  15. $_GET['slug'] = (string) $_GET['lang'];
  16. unset($_GET['lang']);
  17. require __DIR__ . '/page.php';
  18. exit;
  19. }
  20. ​
  21. define('CURRENT_PAGE', 'home');
  22. flatly_set_page_ref('index');
  23. ​
  24. $page_data = load_page_lang('index', flatly_current_lang()) ?? load_page('index');
  25. if (!$page_data) {
  26. $page_data = ['title' => 'Welcome', 'description' => 'Welcome to FlatlyPage', 'blocks' => []];
  27. }
  28. ​
  29. $site_settings = get_site_settings();
  30. ​
  31. $page_title = $page_data['title'] ?? $site_settings['site_name'] ?? SITE_NAME;
  32. $page_description = $page_data['description'] ?? $site_settings['site_description'] ?? '';
  33. $lang = isset($site_settings['site_language']) ? $site_settings['site_language'] : 'en';
  34. $blocks = $page_data['blocks'] ?? [];
  35. $has_newsletter = false;
  36. ​
  37. foreach ($blocks as $block) {
  38. if (($block['type'] ?? '') === 'newsletter') {
  39. $has_newsletter = true;
  40. break;
  41. }
  42. }
  43. ​
  44. $nav_links = $site_settings['nav_links'] ?? [];
  45. $nav_buttons = $site_settings['nav_buttons'] ?? [];
  46. $footer = $site_settings['footer'] ?? [];
  47. $logo_text = $site_settings['logo_text'] ?? $site_settings['site_name'] ?? SITE_NAME;
  48. $logo_image = $site_settings['logo_image'] ?? '';
  49. ​
  50. $website_font = get_text_font($site_settings);
  51. ​
  52. $search_status_file = dirname(__FILE__) . '/data/search/search.txt';
  53. $show_search = false;
  54. ​
  55. if (file_exists($search_status_file)) {
  56. $status_content = trim(file_get_contents($search_status_file));
  57. if ($status_content === 'true') {
  58. $show_search = true;
  59. }
  60. }
  61. ?>
  62. <!-- Powered by FlatlyPage CMS -->
  63. <!DOCTYPE html>
  64. <html lang="<?php echo htmlspecialchars($lang); ?>" data-cms="flatlypage">
  65. ​
  66. <head>
  67. <meta charset="UTF-8">
  68. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  69. <title><?= e($page_title) ?></title>
  70. <meta name="description" content="<?= e($page_description) ?>">
  71. <meta name="generator" content="FlatlyPage CMS">
  72. <?= flatly_canonical_link() ?>
  73. <?= flatly_hreflang_links() ?>
  74. <?= flatly_language_detect_script() ?>
  75. <?php if (!empty($site_settings['favicon'])): ?>
  76. <link rel="icon" href="<?= e($site_settings['favicon']) ?>">
  77. <?php endif; ?>
  78. <?= font_head_html(site_all_fonts($site_settings), [get_site_interface_font($site_settings), $website_font]) ?>
  79. ​
  80. <link rel="stylesheet" href="/css/styles.css?v=<?= filemtime(__DIR__ . '/css/styles.css') ?>">
  81. ​
  82. <?= theme_link_html() ?>
  83. ​
  84. <style>
  85. <?= site_fonts_css($site_settings) ?>
  86. </style>
  87. ​
  88. <script type="application/ld+json">
  89. {
  90. "@context": "https://schema.org",
  91. "@type": "WebSite",
  92. "name": "<?= e($site_settings['site_name'] ?? SITE_NAME) ?>",
  93. "url": "<?= e(SITE_URL) ?>",
  94. "description": "<?= e($site_settings['site_description'] ?? '') ?>",
  95. "publisher": {
  96. "@type": "Organization",
  97. "name": "<?= e($logo_text) ?>"
  98. }
  99. }
  100. </script>
  101. ​
  102. <?= flatly_custom_html('head') ?>
  103. </head>
  104. ​
  105. <body>
  106. <?= flatly_custom_html('body_start') ?>
  107. <nav class="navbar">
  108. <div class="container navbar-inner">
  109. <a href="<?= e(flatly_home_url()) ?>" class="logo">
  110. <?php if (!empty($logo_image)): ?>
  111. <img src="<?= e($logo_image) ?>" alt="<?= e($logo_text) ?>">
  112. <?php endif; ?>
  113. <?= e($logo_text) ?>
  114. </a>
  115. <?php if (!empty($nav_links)): ?>
  116. <div class="nav-links">
  117. <?php foreach ($nav_links as $link): ?>
  118. <a href="<?= e($link['url'] ?? '#') ?>"><?= nav_icon_html($link['icon'] ?? '') ?><?= e($link['label'] ?? '') ?></a>
  119. <?php endforeach; ?>
  120. </div>
  121. <?php endif; ?>
  122. <?php if (!empty($nav_buttons) || $show_search): ?>
  123. <div class="nav-buttons">
  124. <?php if ($show_search): ?>
  125. <a href="/search.php" class="btn btn-search" title="Search" style="padding: 8px; display: inline-flex; align-items: center; margin-right: 5px;">
  126. <svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
  127. <path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
  128. </svg>
  129. </a>
  130. <?php endif; ?>
  131. ​
  132. <?php foreach ($nav_buttons as $btn): ?>
  133. <a href="<?= e($btn['url'] ?? '#') ?>" class="btn btn-<?= e($btn['style'] ?? 'ghost') ?>"><?= nav_icon_html($btn['icon'] ?? '') ?><?= e($btn['label'] ?? '') ?></a>
  134. <?php endforeach; ?>
  135. </div>
  136. <?php endif; ?>
  137. <?= flatly_custom_html('nav') ?>
  138. <button class="mobile-menu-btn" aria-label="Menu" onclick="document.querySelector('.mobile-nav').classList.toggle('active')">
  139. <svg width="24" height="24" fill="none" stroke="currentColor" stroke-width="2">
  140. <path d="M4 6h16M4 12h16M4 18h16" />
  141. </svg>
  142. </button>
  143. </div>
  144. </nav>
  145. <div class="mobile-nav">
  146. <?php if (!empty($nav_links)): ?>
  147. <?php foreach ($nav_links as $link): ?>
  148. <a href="<?= e($link['url'] ?? '#') ?>"><?= nav_icon_html($link['icon'] ?? '') ?><?= e($link['label'] ?? '') ?></a>
  149. <?php endforeach; ?>
  150. <?php endif; ?>
  151. ​
  152. <?php if (!empty($nav_buttons)): ?>
  153. <?php foreach ($nav_buttons as $btn): ?>
  154. <a href="<?= e($btn['url'] ?? '#') ?>" class="btn btn-<?= e($btn['style'] ?? 'ghost') ?>"><?= nav_icon_html($btn['icon'] ?? '') ?><?= e($btn['label'] ?? '') ?></a>
  155. <?php endforeach; ?>
  156. <?php endif; ?>
  157. ​
  158. <?php if ($show_search): ?>
  159. <a href="/search.php" class="btn btn-search" title="Search" style="padding: 8px; display: inline-flex; align-items: center; margin-right: 5px;">
  160. <svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
  161. <path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
  162. </svg>
  163. </a>
  164. <?php endif; ?>
  165. </div>
  166. ​
  167. <main>
  168. <?php if (!empty($blocks)): ?>
  169. <?php foreach ($blocks as $block):
  170. echo apply_bbcode(render_block($block));
  171. endforeach; ?>
  172. <?php endif; ?>
  173. ​
  174. <?php
  175. $site_settings = get_site_settings();
  176. $limit = 6;
  177. $cache_time = 60;
  178. ​
  179. if ($site_settings['recent_pages_enabled'] ?? false):
  180. $all_pages = get_all_products();
  181. ​
  182. $all_pages = array_filter($all_pages, function ($page) {
  183. $slug_valid = ($page['slug'] ?? '') !== 'contact';
  184. $show_in_latest = $page['show_in_latest'] ?? false;
  185. return $slug_valid && $show_in_latest && is_page_published($page);
  186. });
  187. ​
  188. usort($all_pages, function ($a, $b) {
  189. $timeA = !empty($a['publish_enabled']) && !empty($a['publish_at_utc'])
  190. ? strtotime($a['publish_at_utc'] . ' UTC')
  191. : strtotime($a['created_at'] ?? $a['updated_at'] ?? 'now');
  192. $timeB = !empty($b['publish_enabled']) && !empty($b['publish_at_utc'])
  193. ? strtotime($b['publish_at_utc'] . ' UTC')
  194. : strtotime($b['created_at'] ?? $b['updated_at'] ?? 'now');
  195. return $timeB - $timeA;
  196. });
  197. $recent_pages = array_slice($all_pages, 0, $limit);
  198. ​
  199. $cache_key = md5(serialize(array_map(function ($page) {
  200. $publish_marker = !empty($page['publish_enabled']) && !empty($page['publish_at_utc'])
  201. ? $page['publish_at_utc']
  202. : ($page['created_at'] ?? $page['updated_at'] ?? 'now');
  203. return $publish_marker;
  204. }, $recent_pages)));
  205. $cache_file = __DIR__ . "/cache/pages_grid" . (flatly_current_lang() !== null ? '-' . flatly_current_lang() : '') . ".html";
  206. ​
  207. if (file_exists($cache_file) && (time() - filemtime($cache_file) < $cache_time)) {
  208. include($cache_file);
  209. } else {
  210. ob_start();
  211. ?>
  212. <section class="pages-section" style="margin-bottom: 50px;">
  213. <div class="container">
  214. <h2 style="font-size: 2.5rem; margin-bottom: 40px;">Latest content</h2>
  215. <?php if (empty($recent_pages)): ?>
  216. <div style="text-align: center; padding: 40px; color: var(--text-secondary);">
  217. <p>No pages found.</p>
  218. </div>
  219. <?php else: ?>
  220. <div class="pages-grid" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 24px;">
  221. <?php foreach ($recent_pages as $page): ?>
  222. <article class="pages-card" style="background: var(--card); border: 1px solid var(--border); padding: 24px; border-radius: 12px; display: flex; flex-direction: column;">
  223. <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
  224. <span style="color: var(--primary-color); font-size: 0.75rem; font-weight: 700; text-transform: uppercase; letter-spacing: 1px;">
  225. <?= e($page['blocks'][0]['data']['badge'] ?? '') ?>
  226. </span>
  227. <time style="color: var(--text-secondary); font-size: 0.7rem;">
  228. <?php
  229. $card_date = !empty($page['publish_enabled']) && !empty($page['publish_at_utc'])
  230. ? get_publish_date_only($page)
  231. : date('d.m.Y', strtotime($page['created_at'] ?? $page['updated_at'] ?? 'now'));
  232. echo e($card_date);
  233. ?>
  234. </time>
  235. </div>
  236. <h3 style="margin: 0 0 12px 0; font-size: 1.4rem; color: var(--text);">
  237. <?= e($page['title'] ?? 'No title') ?>
  238. </h3>
  239. <p style="color: var(--text-secondary); line-height: 1.6; font-size: 0.95rem; flex-grow: 1;">
  240. <?= e(mb_strimwidth($page['description'] ?? '', 0, 120, "...")) ?>
  241. </p>
  242. <a href="<?= e(flatly_lang_url(flatly_current_lang(), '/' . ($page['slug'] ?? ''))) ?>" style="display: inline-block; margin-top: 20px; color: var(--primary-color); text-decoration: none; font-weight: 600; font-size: 0.9rem;">
  243. Learn more <span style="margin-left: 5px;">→</span>
  244. </a>
  245. </article>
  246. <?php endforeach; ?>
  247. </div>
  248. <?php endif; ?>
  249. </div>
  250. </section>
  251. <?php
  252. $output = ob_get_clean();
  253. if (!is_dir(__DIR__ . '/cache')) {
  254. mkdir(__DIR__ . '/cache', 0755, true);
  255. }
  256. file_put_contents($cache_file, $output);
  257. echo $output;
  258. }
  259. endif;
  260. ?>
  261. ​
  262. </main>
  263. ​
  264. <footer class="footer">
  265. <div class="container">
  266. <div class="footer-grid">
  267. <div class="footer-brand">
  268. <h3>
  269. <?php if (!empty($logo_image)): ?>
  270. <img src="<?= e($logo_image) ?>" alt="<?= e($logo_text) ?>">
  271. <?php endif; ?>
  272. <?= e($logo_text) ?>
  273. </h3>
  274. <p><?= e($footer['brand_description'] ?? '') ?></p>
  275. <?php if (!empty($footer['social_links'])): ?>
  276. <div class="social-links">
  277. <?php foreach ($footer['social_links'] as $social): ?>
  278. <a href="<?= e($social['url'] ?? '#') ?>" aria-label="<?= e(ucfirst($social['platform'] ?? '')) ?>"><?= render_social_icon($social['platform'] ?? 'twitter') ?></a>
  279. <?php endforeach; ?>
  280. </div>
  281. <?php endif; ?>
  282. </div>
  283. <?php if (!empty($footer['columns'])): ?>
  284. <?php foreach ($footer['columns'] as $column): ?>
  285. <div class="footer-column">
  286. <h4><?= e($column['title'] ?? '') ?></h4>
  287. <?php if (!empty($column['links'])): ?>
  288. <ul>
  289. <?php foreach ($column['links'] as $link): ?>
  290. <li><a href="<?= e($link['url'] ?? '#') ?>"><?= e($link['label'] ?? '') ?></a></li>
  291. <?php endforeach; ?>
  292. </ul>
  293. <?php endif; ?>
  294. </div>
  295. <?php endforeach; ?>
  296. <?php endif; ?>
  297. </div>
  298. <div class="footer-bottom">
  299. <div style="flex: 1;">
  300. <p><?= e($footer['copyright'] ?? '© ' . date('Y') . ' ' . $logo_text . '. All rights reserved.') ?></p>
  301. <?php if (!empty($footer['bottom_links'])): ?>
  302. <div class="footer-legal">
  303. <?php foreach ($footer['bottom_links'] as $link): ?>
  304. <a href="<?= e($link['url'] ?? '#') ?>"><?= e($link['label'] ?? '') ?></a>
  305. <?php endforeach; ?>
  306. </div>
  307. <?php endif; ?>
  308. </div>
  309. ​
  310. ​
  311. <?= render_language_switcher() ?>
  312. ​
  313. <button class="theme-toggle" aria-label="Toggle theme" title="Toggle light/dark theme">
  314. <svg class="theme-icon-sun" viewBox="0 0 24 24" style="display: none;">
  315. <circle cx="12" cy="12" r="5" />
  316. <line x1="12" y1="1" x2="12" y2="3" />
  317. <line x1="12" y1="21" x2="12" y2="23" />
  318. <line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
  319. <line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
  320. <line x1="1" y1="12" x2="3" y2="12" />
  321. <line x1="21" y1="12" x2="23" y2="12" />
  322. <line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
  323. <line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
  324. </svg>
  325. <svg class="theme-icon-moon" viewBox="0 0 24 24" style="display: block;">
  326. <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
  327. </svg>
  328. </button>
  329. </div>
  330. </div>
  331. </footer>
  332. ​
  333. <script>
  334. const themeToggle = document.querySelector('.theme-toggle');
  335. const sunIcon = document.querySelector('.theme-icon-sun');
  336. const moonIcon = document.querySelector('.theme-icon-moon');
  337. const html = document.documentElement;
  338. ​
  339. function initTheme() {
  340. let theme = localStorage.getItem('theme');
  341. if (!theme) {
  342. const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
  343. theme = prefersDark ? 'dark' : 'light';
  344. }
  345. setTheme(theme);
  346. }
  347. ​
  348. function setTheme(theme) {
  349. if (theme === 'light') {
  350. html.setAttribute('data-theme', 'light');
  351. sunIcon.style.display = 'block';
  352. moonIcon.style.display = 'none';
  353. localStorage.setItem('theme', 'light');
  354. } else {
  355. html.removeAttribute('data-theme');
  356. sunIcon.style.display = 'none';
  357. moonIcon.style.display = 'block';
  358. localStorage.setItem('theme', 'dark');
  359. }
  360. }
  361. ​
  362. function toggleTheme() {
  363. const currentTheme = html.getAttribute('data-theme') || 'dark';
  364. const newTheme = currentTheme === 'light' ? 'dark' : 'light';
  365. setTheme(newTheme);
  366. }
  367. ​
  368. if (themeToggle) {
  369. themeToggle.addEventListener('click', toggleTheme);
  370. }
  371. ​
  372. initTheme();
  373. ​
  374. document.querySelectorAll('.mobile-nav a').forEach(link => {
  375. link.addEventListener('click', () => {
  376. document.querySelector('.mobile-nav').classList.remove('active');
  377. });
  378. });
  379. </script>
  380. ​
  381. <?php if ($has_newsletter): ?>
  382. <script src="/newsletter/newsletter-form.js?v=<?= date('YmdH') ?>"></script>
  383. <?php endif; ?>
  384. <?= flatly_custom_html('body_end') ?>
  385. <?= flatly_custom_js() ?>
  386. </body>
  387. ​
  388. </html>