WebOrbiton
v3.0.0.2

FlatlyPage

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