/** * SiteForge Privacy Manager * Gestisce il consenso ai cookie e la privacy (GDPR/CCPA compliant basics). * * Utilizzo: * 1. Includi questo script: * 2. Inizializza: * PrivacyManager.init({ * policyUrl: '/privacy-policy', * cookiePolicyUrl: '/cookie-policy' * }); */ $( document ).ready(function() {PrivacyManager.init({policyUrl: '/privacy-policy.htm', cookiePolicyUrl: '/cookie-policy.html'});}); const PrivacyManager = (function () { const STORAGE_KEY = 'siteforge_privacy_consent'; // Default config let config = { policyUrl: '#', cookiePolicyUrl: '#', autoBlock: true // Blocca script src se non autorizzati (richiede gestione specifica) }; let consent = { necessary: true, // Sempre true analytics: false, marketing: false, timestamp: null }; // --- UI TEMPLATES --- const css = ` #sf-privacy-banner { position: fixed; bottom: 0; left: 0; width: 100%; background: #fff; box-shadow: 0 -2px 10px rgba(0,0,0,0.1); padding: 20px; font-family: sans-serif; font-size: 14px; z-index: 99999; display: none; flex-direction: column; gap: 15px; border-top: 1px solid #eee; } @media (min-width: 768px) { #sf-privacy-banner { flex-direction: row; align-items: center; justify-content: space-between; padding: 20px 40px; } } .sf-privacy-buttons { display: flex; gap: 10px; flex-wrap: wrap; } .sf-btn { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; font-weight: 600; font-size: 13px; } .sf-btn-primary { background: #1a73e8; color: white; } .sf-btn-secondary { background: #f1f3f4; color: #333; } .sf-btn-link { background: none; text-decoration: underline; color: #555; } #sf-privacy-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 100000; display: none; align-items: center; justify-content: center; } .sf-privacy-modal-content { background: white; padding: 30px; border-radius: 8px; width: 90%; max-width: 500px; max-height: 90vh; overflow-y: auto; font-family: sans-serif; } .sf-switch-group { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; padding-bottom: 15px; border-bottom: 1px solid #eee; } .sf-switch-label { font-weight: bold; display: block; } .sf-switch-desc { font-size: 12px; color: #666; display: block; margin-top: 5px; } `; function init(userConfig) { config = { ...config, ...userConfig }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', runStartLogic); } else { runStartLogic(); } } function runStartLogic() { // Load saved consent const saved = localStorage.getItem(STORAGE_KEY); if (saved) { consent = JSON.parse(saved); // Apply saved consent (e.g. enable scripts) applyConsent(); } else { // Show Banner injectStyles(); injectBanner(); } } function injectStyles() { const style = document.createElement('style'); style.textContent = css; document.head.appendChild(style); } function injectBanner() { $('#sf-privacy-banner').remove(); if (document.getElementById('sf-privacy-banner')) return; const banner = document.createElement('div'); banner.id = 'sf-privacy-banner'; banner.innerHTML = `
Questo sito utilizza i cookie.

Utilizziamo cookie tecnici e, previo consenso, cookie di profilazione e analitici per migliorare la tua esperienza. Puoi gestire le tue preferenze o accettare tutti i cookie. Leggi la Privacy Policy.

`; document.body.appendChild(banner); // Animations setTimeout(() => banner.style.display = 'flex', 100); // Events document.getElementById('sf-privacy-accept').onclick = acceptAll; document.getElementById('sf-privacy-reject').onclick = rejectAll; document.getElementById('sf-privacy-settings').onclick = openSettings; } function injectSettingsModal() { if (document.getElementById('sf-privacy-modal')) { document.getElementById('sf-privacy-modal').style.display = 'flex'; return; } const modal = document.createElement('div'); modal.id = 'sf-privacy-modal'; modal.innerHTML = `

Preferenze Privacy

Gestisci il consenso per le diverse categorie di cookie.

Necessari Indispensabili per il funzionamento del sito. Non possono essere disattivati.
Analitici Ci aiutano a capire come visiti il sito (es. Google Analytics).
Marketing Usati per inviare pubblicità mirata (es. Facebook Pixel).
`; document.body.appendChild(modal); modal.style.display = 'flex'; // Events document.getElementById('sf-modal-cancel').onclick = () => { modal.style.display = 'none'; }; document.getElementById('sf-modal-save').onclick = () => { consent.analytics = document.getElementById('sf-consent-analytics').checked; consent.marketing = document.getElementById('sf-consent-marketing').checked; saveConsent(); modal.style.display = 'none'; }; } function acceptAll() { consent.analytics = true; consent.marketing = true; saveConsent(); } function rejectAll() { consent.analytics = false; consent.marketing = false; saveConsent(); } function openSettings() { injectSettingsModal(); } function saveConsent() { consent.timestamp = new Date().toISOString(); localStorage.setItem(STORAGE_KEY, JSON.stringify(consent)); const banner = document.getElementById('sf-privacy-banner'); if (banner) banner.style.display = 'none'; applyConsent(); } function applyConsent() { console.log("SiteForge Privacy: Consent Applied", consent); // Event for other scripts const event = new CustomEvent('siteforge-consent-update', { detail: consent }); window.dispatchEvent(event); // Example: Enable Google Analytics if accepted if (consent.analytics) { enableAnalytics(); } } function enableAnalytics() { // Here you would typically inject the GA script tag dynamically // or change types from text/plain to text/javascript // Example logic needed here based on user specific scripts } // Public API return { init: init, openSettings: openSettings, getConsent: () => consent }; })(); $(document).ready(function () { // Configuration const API_URL = 'blog_manager.php'; // 1. Check if we need to render the Blog Grid const $gridContainer = $('.siteforge-blog-grid-container'); if ($gridContainer.length) { loadBlogGrid($gridContainer); } // 2. Check if we need to render a Single Post const $postContainer = $('.siteforge-blog-post-container'); if ($postContainer.length) { const urlParams = new URLSearchParams(window.location.search); const slug = urlParams.get('post_slug'); const id = urlParams.get('post_id'); if (slug || id) { loadSinglePost($postContainer, slug, id); } else { $postContainer.html('

Seleziona un post dalla pagina del blog.

'); } } // 3. Blog Explorer: elenco completo dei post + calendario/archivio laterale + ricerca const $blogExplorers = $('.siteforge-blog-explorer'); if ($blogExplorers.length) { $blogExplorers.each(function () { loadBlogExplorer($(this)); }); } // Functions function loadBlogGrid($container) { $container.html('
'); $.get(API_URL, { action: 'list_posts' }, function (response) { if (response.status === 'success') { $container.empty(); if (response.posts.length === 0) { $container.html('

Nessun post trovato.

'); return; } // Filter only published if on public frontend? // For now show all or check status. ideal: show only published. const publicPosts = response.posts.filter(p => p.status === 'published'); if (publicPosts.length === 0) { $container.html('

Nessun post pubblicato.

'); return; } // Quanti post mostrare: attributo data-limit sul container (override // per-istanza) oppure impostazione globale grid_limit dal backoffice. // 0 / assente / non valido = mostra tutti. const settings = response.settings || {}; const attrLimit = parseInt($container.data('limit'), 10); const cfgLimit = parseInt(settings.grid_limit, 10); let gridLimit = 0; if (!isNaN(attrLimit) && attrLimit > 0) gridLimit = attrLimit; else if (!isNaN(cfgLimit) && cfgLimit > 0) gridLimit = cfgLimit; const shownPosts = gridLimit > 0 ? publicPosts.slice(0, gridLimit) : publicPosts; shownPosts.forEach(post => { const date = new Date(post.created_at).toLocaleDateString(); const excerpt = post.excerpt || (post.content ? post.content.substring(0, 100) + '...' : ''); // Strip HTML from excerpt if it came from content const cleanExcerpt = excerpt.replace(/<[^>]*>?/gm, ''); const cover = post.cover_image || 'https://via.placeholder.com/400x250?text=Blog'; // Link logic: We need to know the page that acts as "Single Post View". // For now, we assume the user might link to the SAME page with a query param // OR user sets up a specific page. // Let's assume the user will put the Single Post Strip on a page called 'post.htm' or similar. // BUT, to be generic, we can link to "post.htm?post_slug=..." if it exists, // or just current page? // Better: The user should probably configure the "Post Page Base URL". // For simplicity, we'll assume a standard 'blog-post.htm' or similar, // OR we just link to "?post_slug=..." and assume the *current* page can handle it? // No, usually grid is on 'blog.htm' and single is on 'post.htm'. // We'll use a data attribute on the container if set, else default. const basePostUrl = $container.data('post-page') || 'post.htm'; // Fallback const link = `${basePostUrl}?post_slug=${post.slug}`; const card = `
${post.title}
${date}

${post.title}

${cleanExcerpt}

Leggi tutto →
`; $container.append(card); }); } else { $container.html('

Errore caricamento blog.

'); } }, 'json').fail(function () { $container.html('

Errore di connessione.

'); }); } // Riconosce un file video per estensione (upload diretto, non link esterno) function isBlogVideoFile(url) { return /\.(mp4|webm|ogg|mov|m4v)(\?.*)?$/i.test(String(url || '')); } // Estensioni "documenti" che, se caricati nella cartella media del post, // diventano automaticamente allegati scaricabili in fondo al post // pubblico — nessuna selezione manuale, nessuna colonna DB dedicata. const BLOG_ATTACHMENT_EXT = /\.(pdf|docx?|xlsx?|pptx?)$/i; const BLOG_ATTACHMENT_ICONS = { pdf: 'fa-file-pdf', doc: 'fa-file-word', docx: 'fa-file-word', xls: 'fa-file-excel', xlsx: 'fa-file-excel', ppt: 'fa-file-powerpoint', pptx: 'fa-file-powerpoint', }; // video_url del post -> YouTube/Vimeo embed, file video caricato ->