v1.0.0.8
Publisium
- <?php
-
- declare(strict_types=1);
-
- require_once __DIR__ . '/footer-links.php';
- require_once __DIR__ . '/social-icons.php';
-
- $footerColumns = Languages::footerColumns(FooterLinks::decodeColumns($settings['footer_columns'] ?? '[]'));
- $footerSocialLinks = FooterLinks::decodeSocialLinks($settings['footer_social_links'] ?? '[]');
- $consentCategories = SiteFront::consentCategories();
-
- ?>
- </main>
-
- </div>
-
- <?php foreach (SiteFront::activeAds('footer') as $footerAd): ?>
- <?= SiteFront::renderAd($footerAd) ?>
- <?php endforeach; ?>
-
- <footer class="site-footer">
-
- <?php if (!empty($footerColumns)): ?>
- <div class="site-footer-columns">
- <?php foreach ($footerColumns as $column): ?>
- <div>
- <h3 class="site-footer-column-title"><?= htmlspecialchars($column['title'] ?? '') ?></h3>
- <div class="site-footer-column-links">
- <?php foreach ($column['links'] ?? [] as $link): ?>
- <a href="<?= htmlspecialchars($link['url'] ?? '') ?>"><?= htmlspecialchars($link['label'] ?? '') ?></a>
- <?php endforeach; ?>
- </div>
- </div>
- <?php endforeach; ?>
- </div>
- <?php endif; ?>
-
- <div class="site-footer-bottom">
- <div class="site-footer-row">
- <span>© <?= date('Y') ?> <?= htmlspecialchars($settings['site_name'] ?? '') ?>. <?= htmlspecialchars(Language::get('footer_rights', 'All rights reserved.')) ?></span>
-
- <div class="site-footer-links">
- <?php if ($settings['sitemap_rss_enabled'] === '1'): ?>
- <a href="sitemap.php"><?= htmlspecialchars(Language::get('footer_sitemap', 'Sitemap')) ?></a>
- <a href="rss.php"><?= htmlspecialchars(Language::get('footer_rss', 'RSS')) ?></a>
- <?php endif; ?>
- <?= SiteFront::renderSearchControl('footer') ?>
- <?php if ($consentCategories !== [] && ($settings['consent_footer_icon_enabled'] ?? '0') === '1'): ?>
- <button type="button" id="consent-reopen-btn" aria-label="<?= htmlspecialchars(Language::get('consent_manage', 'Manage preferences')) ?>" title="<?= htmlspecialchars(Language::get('consent_manage', 'Manage preferences')) ?>">
- <svg class="icon icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 2a10 10 0 1 0 10 10 4 4 0 0 1-5-5 4 4 0 0 1-5-5"/><path d="M8.5 8.5v.01"/><path d="M16 15.5v.01"/><path d="M12 12v.01"/><path d="M11 17v.01"/><path d="M7 14v.01"/></svg>
- </button>
- <?php endif; ?>
- <?= Languages::renderSwitcher() ?>
- <button type="button" id="theme-toggle-btn" aria-label="<?= htmlspecialchars(Language::get('theme_toggle', 'Switch between light and dark mode'), ENT_QUOTES) ?>">
- <?= Icons::icon('sun', 'icon icon-sm icon-sun') ?>
- <?= Icons::icon('moon', 'icon icon-sm icon-moon') ?>
- </button>
- </div>
-
- <?php if (!empty($footerSocialLinks)): ?>
- <div class="site-footer-social">
- <?php foreach ($footerSocialLinks as $social): ?>
- <a href="<?= htmlspecialchars($social['url'] ?? '') ?>" target="_blank" rel="noopener noreferrer" aria-label="<?= htmlspecialchars($social['label'] ?? '') ?>">
- <?php if (($social['key'] ?? '') === 'custom'): ?>
- <?= $social['svg'] ?? '' ?>
- <?php else: ?>
- <?= SocialIcons::builtinSvg((string) ($social['key'] ?? '')) ?>
- <?php endif; ?>
- </a>
- <?php endforeach; ?>
- </div>
- <?php endif; ?>
- </div>
- </div>
-
- </footer>
-
- <?php
- $appBannerAndroidUrl = trim((string) ($settings['app_android_url'] ?? ''));
- $appBannerIosUrl = trim((string) ($settings['app_ios_url'] ?? ''));
- $appBannerEnabled = ($settings['app_banner_enabled'] ?? '0') === '1' && ($appBannerAndroidUrl !== '' || $appBannerIosUrl !== '');
- ?>
- <?php if ($appBannerEnabled): ?>
- <?php $appBannerIcon = !empty($settings['pwa_icon_url']) ? $settings['pwa_icon_url'] : ($settings['site_logo_url'] ?? ''); ?>
- <div class="app-banner" id="app-banner" data-android-url="<?= htmlspecialchars($appBannerAndroidUrl, ENT_QUOTES) ?>" data-ios-url="<?= htmlspecialchars($appBannerIosUrl, ENT_QUOTES) ?>">
- <?php if (!empty($appBannerIcon)): ?>
- <img src="<?= htmlspecialchars($appBannerIcon) ?>" alt="" class="app-banner-icon">
- <?php endif; ?>
- <span class="app-banner-text"><?= htmlspecialchars(Language::get('app_available_text', 'Also available as an app')) ?></span>
- <a href="#" class="app-banner-link" id="app-banner-link" target="_blank" rel="noopener noreferrer"><?= htmlspecialchars(Language::get('app_get_button', 'Get the app')) ?></a>
- <button type="button" class="app-banner-close" id="app-banner-close" aria-label="<?= htmlspecialchars(Language::get('app_dismiss', 'Dismiss')) ?>">×</button>
- </div>
- <?php endif; ?>
-
- <?php if (($settings['pwa_enabled'] ?? '0') === '1'): ?>
- <script>
- if ('serviceWorker' in navigator) {
- window.addEventListener('load', function () {
- navigator.serviceWorker.register('<?= htmlspecialchars(rtrim(Config::get('APP_BASE_PATH', ''), '/')) ?>/sw.js');
- });
- }
- </script>
- <?php endif; ?>
-
- <?php $stickyAdsHtml = SiteFront::renderAds('sticky-bottom'); ?>
- <?php if ($stickyAdsHtml !== ''): ?>
- <div class="ad-sticky" id="ad-sticky">
- <?= $stickyAdsHtml ?>
- <button type="button" class="ad-sticky-close" id="ad-sticky-close" aria-label="<?= htmlspecialchars(Language::get('ad_close', 'Close ad'), ENT_QUOTES) ?>"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6 6 18"/></svg></button>
- </div>
- <script>
- (function () {
- var bar = document.getElementById('ad-sticky');
- if (!bar) return;
- var body = document.body;
- var closed = false;
- // Ads that wait for cookie consent are still an empty <template>, so the bar stays hidden until one loads.
- function hasAd() {
- return Array.prototype.some.call(bar.querySelectorAll('.ad-slot'), function (slot) {
- return Array.prototype.some.call(slot.childNodes, function (node) {
- return (node.nodeType === 1 && node.tagName !== 'TEMPLATE') || (node.nodeType === 3 && node.textContent.trim() !== '');
- });
- });
- }
- function sync() {
- if (closed) {
- return;
- }
- var show = hasAd();
- bar.hidden = !show;
- body.classList.toggle('has-ad-sticky', show);
- if (show) {
- body.style.setProperty('--ad-sticky-h', bar.offsetHeight + 'px');
- } else {
- body.style.removeProperty('--ad-sticky-h');
- }
- }
- sync();
- if (window.ResizeObserver) {
- new ResizeObserver(sync).observe(bar);
- } else {
- window.addEventListener('resize', sync);
- }
- if (window.MutationObserver) {
- new MutationObserver(sync).observe(bar, { childList: true, subtree: true });
- }
- document.getElementById('ad-sticky-close').addEventListener('click', function () {
- closed = true;
- bar.remove();
- body.classList.remove('has-ad-sticky');
- body.style.removeProperty('--ad-sticky-h');
- });
- })();
- </script>
- <?php endif; ?>
-
- <?php if (($settings['adblock_notice_enabled'] ?? '0') === '1' && !SiteFront::visitorHasSubscription()): ?>
- <?php
- $adblockMode = SiteFront::adblockMode();
- $adblockLocked = in_array($adblockMode, ['lock', 'lock_max'], true);
- $adblockClasses = 'adblock-notice ' . ($adblockLocked ? 'adblock-notice-wall' : 'adblock-notice-gentle') . ($adblockMode === 'lock_max' ? ' adblock-notice-max' : '');
- ?>
- <div class="adsbygoogle ad-banner textads banner-ads ad-placement" id="adblock-bait" aria-hidden="true"> </div>
- <div class="<?= $adblockClasses ?>" id="adblock-notice" data-mode="<?= htmlspecialchars($adblockMode, ENT_QUOTES) ?>" role="dialog" aria-modal="<?= $adblockLocked ? 'true' : 'false' ?>" aria-labelledby="adblock-notice-title" hidden>
- <div class="adblock-notice-box">
- <p class="adblock-notice-title" id="adblock-notice-title"><?= htmlspecialchars(Language::get('adblock_title', 'Looks like you’re using an ad blocker')) ?></p>
- <p class="adblock-notice-text"><?= htmlspecialchars(Language::get('adblock_text', 'Ads help us keep this site free for everyone. Please turn off your ad blocker for this site, or add it to your allow list.')) ?></p>
- <div class="adblock-notice-actions">
- <button type="button" id="adblock-notice-reload"><?= htmlspecialchars(Language::get('adblock_reload', 'I’ve turned it off, reload the page')) ?></button>
- <?php if (!$adblockLocked): ?>
- <button type="button" id="adblock-notice-dismiss" class="adblock-notice-secondary"><?= htmlspecialchars(Language::get('adblock_dismiss', 'Continue anyway')) ?></button>
- <?php endif; ?>
- </div>
- <?php if (($settings['subscription_checkout_url'] ?? '') !== ''): ?>
- <a href="<?= htmlspecialchars((string) $settings['subscription_checkout_url'], ENT_QUOTES) ?>" class="adblock-notice-subscribe"><?= htmlspecialchars(Language::get('adblock_subscribe', 'Or read without ads with a subscription')) ?></a>
- <?php endif; ?>
- </div>
- </div>
- <?= Asset::js('assets/ads.js') ?>
- <script>
- // Runs only in the browser: the result is never sent anywhere.
- (function () {
- var notice = document.getElementById('adblock-notice');
- var bait = document.getElementById('adblock-bait');
- var mode = notice.getAttribute('data-mode');
- var locked = mode === 'lock' || mode === 'lock_max';
- var SEEN_KEY = 'publisium_adblock_seen';
-
- // "once" remembers in localStorage, "session" in sessionStorage; both stay in this browser only.
- function storage() {
- try {
- return mode === 'once' ? window.localStorage : (mode === 'session' ? window.sessionStorage : null);
- } catch (e) {
- return null;
- }
- }
-
- function alreadySeen() {
- var store = storage();
- try { return store !== null && store.getItem(SEEN_KEY) === '1'; } catch (e) { return false; }
- }
-
- function rememberSeen() {
- var store = storage();
- try { if (store !== null) { store.setItem(SEEN_KEY, '1'); } } catch (e) {}
- }
-
- function baitHidden() {
- if (!bait || !bait.isConnected) {
- return true;
- }
- var style = window.getComputedStyle(bait);
- return bait.offsetHeight === 0 || style.display === 'none' || style.visibility === 'hidden';
- }
-
- function lockPage() {
- document.body.appendChild(notice);
- document.documentElement.classList.add('adblock-locked');
- if (mode === 'lock_max') {
- document.documentElement.classList.add('adblock-max');
- }
- Array.prototype.forEach.call(document.body.children, function (element) {
- if (element !== notice && element.tagName !== 'SCRIPT') {
- element.setAttribute('inert', '');
- element.setAttribute('aria-hidden', 'true');
- }
- });
- }
-
- function check() {
- var blocked = window.publisiumAdsLoaded !== true || baitHidden();
- if (bait && bait.isConnected) {
- bait.remove();
- }
- if (!blocked || alreadySeen()) {
- return;
- }
- notice.hidden = false;
- rememberSeen();
- if (locked) {
- lockPage();
- }
- document.getElementById('adblock-notice-reload').focus();
- }
-
- document.getElementById('adblock-notice-reload').addEventListener('click', function () {
- window.location.reload();
- });
- var dismiss = document.getElementById('adblock-notice-dismiss');
- if (dismiss) {
- dismiss.addEventListener('click', function () {
- notice.hidden = true;
- });
- }
-
- if (document.readyState === 'complete') {
- setTimeout(check, 200);
- } else {
- window.addEventListener('load', function () { setTimeout(check, 200); });
- }
- })();
- </script>
- <?php endif; ?>
-
- <?php if (($settings['back_to_top_enabled'] ?? '0') === '1'): ?>
- <button type="button" class="back-to-top" id="back-to-top" aria-label="<?= htmlspecialchars(Language::get('back_to_top', 'Back to top')) ?>" title="<?= htmlspecialchars(Language::get('back_to_top', 'Back to top')) ?>" tabindex="-1" data-progress="<?= ($settings['back_to_top_progress_enabled'] ?? '0') === '1' ? '1' : '0' ?>" data-left-text="<?= htmlspecialchars(Language::get('reading_progress_left', '{percent}% left'), ENT_QUOTES) ?>">
- <svg class="icon icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 19V5"/><path d="M5 12l7-7 7 7"/></svg>
- <span class="back-to-top-progress" hidden></span>
- </button>
- <?php endif; ?>
-
- <?php if ($consentCategories !== []): ?>
- <div class="consent-banner" id="consent-banner" role="dialog" aria-label="<?= htmlspecialchars(Language::get('consent_manage', 'Manage preferences'), ENT_QUOTES) ?>" data-categories="<?= htmlspecialchars(implode(' ', $consentCategories), ENT_QUOTES) ?>">
- <p><?= htmlspecialchars(Language::get('consent_text', 'We use cookies and similar technologies to improve your experience.')) ?></p>
- <div class="consent-options" id="consent-options" hidden>
- <label class="consent-option">
- <input type="checkbox" checked disabled>
- <span><strong><?= htmlspecialchars(Language::get('consent_necessary', 'Necessary')) ?></strong><?= htmlspecialchars(Language::get('consent_necessary_desc', 'Needed for the site to work, for example to remember these choices. Always on.')) ?></span>
- </label>
- <?php if (in_array('analytics', $consentCategories, true)): ?>
- <label class="consent-option">
- <input type="checkbox" id="consent-opt-analytics" data-category="analytics">
- <span><strong><?= htmlspecialchars(Language::get('consent_analytics', 'Analytics')) ?></strong><?= htmlspecialchars(Language::get('consent_analytics_desc', 'Helps us see which articles people read, so we can make the site better.')) ?></span>
- </label>
- <?php endif; ?>
- <?php if (in_array('marketing', $consentCategories, true)): ?>
- <label class="consent-option">
- <input type="checkbox" id="consent-opt-marketing" data-category="marketing">
- <span><strong><?= htmlspecialchars(Language::get('consent_marketing', 'Marketing')) ?></strong><?= htmlspecialchars(Language::get('consent_marketing_desc', 'Lets advertising partners show ads that fit your interests.')) ?></span>
- </label>
- <?php endif; ?>
- </div>
- <div class="consent-banner-actions">
- <button type="button" id="consent-decline"><?= htmlspecialchars(Language::get('consent_decline', 'Decline')) ?></button>
- <button type="button" id="consent-manage"><?= htmlspecialchars(Language::get('consent_manage_button', 'Manage')) ?></button>
- <button type="button" id="consent-save" hidden><?= htmlspecialchars(Language::get('consent_save', 'Save choices')) ?></button>
- <button type="button" id="consent-accept"><?= htmlspecialchars(Language::get('consent_accept', 'Accept')) ?></button>
- </div>
- </div>
- <?php endif; ?>
-
- <?= Asset::js('assets/site.js') ?>
- <?= Asset::js('assets/code-block.js') ?>
-
- <?php if (!empty($settings['custom_js'])): ?>
- <script>
- <?= $settings['custom_js'] ?>
- </script>
- <?php endif; ?>
-
- <?= SiteFront::renderCustomBodyHtml($pageType ?? null) ?>
- <?= SiteFront::renderAnalyticsScripts('body_end') ?>
-
- </body>
-
- </html>