diff --git a/inc_footer.php b/inc_footer.php index fec3bbd..e9a2553 100644 --- a/inc_footer.php +++ b/inc_footer.php @@ -114,7 +114,7 @@ if (isset($tabEvenement) && $tabEvenement['general']['eve_commanditaires'] == "1
- cookies + <?php echo ($strLangue === 'fr') ? 'Gérer les témoins' : 'Manage cookies'; ?>
@@ -187,7 +187,16 @@ if (isset($tabEvenement) && $tabEvenement['general']['eve_commanditaires'] == "1 justify-content: center; align-items: center; flex-wrap: wrap; - gap: 10px; + gap: 12px; + text-align: center; + } + + .cookie-consent-container p { + flex: 1 1 100%; + max-width: 900px; + margin: 0 auto 8px; + font-size: 14px; + line-height: 1.5; } .cookie-consent-actions { @@ -200,6 +209,23 @@ if (isset($tabEvenement) && $tabEvenement['general']['eve_commanditaires'] == "1 .cookie-consent-links { margin-top: 10px; text-align: center; + font-size: 13px; + } + + .cookie-consent-links a { + color: #0066cc; + text-decoration: underline; + } + + .ms1-footer-top .social a.cookie-manage-link { + display: inline-block; + vertical-align: middle; + margin-left: 6px; + opacity: 0.9; + } + + .ms1-footer-top .social a.cookie-manage-link:hover { + opacity: 1; } #acceptCookies, #rejectCookies { @@ -225,6 +251,47 @@ if (isset($tabEvenement) && $tabEvenement['general']['eve_commanditaires'] == "1 background-color: #e53935; } + #cookiePreferencesPanel { + max-width: 640px; + margin: 12px auto 0; + padding: 12px 16px; + background: #fff; + border: 1px solid #ddd; + border-radius: 4px; + text-align: left; + } + + .cookie-pref-row { + display: flex; + align-items: flex-start; + gap: 10px; + margin-bottom: 10px; + } + + .cookie-pref-row label { + margin: 0; + font-weight: normal; + font-size: 13px; + } + + .cookie-pref-row input[type="checkbox"] { + margin-top: 3px; + } + + #customizeCookies, #saveCookiePreferences { + background: #666; + border-radius: 5px; + color: white; + border: none; + padding: 10px 20px; + cursor: pointer; + font-size: 12px; + } + + #saveCookiePreferences { + background: #333; + } + @@ -235,64 +302,37 @@ if (isset($tabEvenement) && $tabEvenement['general']['eve_commanditaires'] == "1 + + - + \ No newline at end of file diff --git a/inc_footer_scripts.php b/inc_footer_scripts.php index 6d7bc56..831017c 100644 --- a/inc_footer_scripts.php +++ b/inc_footer_scripts.php @@ -6,7 +6,9 @@ if (fxCookiesAllowMarketing()) { if (isset($tabEvenement['general']) && trim($tabEvenement['general']['eve_plugins']) != '') { echo trim($tabEvenement['general']['eve_plugins']); } +} +if (fxCookiesAllowAnalytics()) { echo $vGoogleAnalytics; } diff --git a/inc_header.php b/inc_header.php index a9917d8..03493a4 100644 --- a/inc_header.php +++ b/inc_header.php @@ -137,7 +137,9 @@ if (!isset($strOGDescription)) { --> + + diff --git a/index.php b/index.php index c193d3e..746e474 100644 --- a/index.php +++ b/index.php @@ -65,8 +65,13 @@ require_once("inc_header.php"); false, 'marketing' => false, 'set' => false]; + + if (!empty($_COOKIE['cookiesPreferences'])) { + $prefs = json_decode(stripslashes((string) $_COOKIE['cookiesPreferences']), true); + + if (is_array($prefs)) { + return [ + 'analytics' => !empty($prefs['analytics']), + 'marketing' => !empty($prefs['marketing']), + 'set' => true, + ]; + } + } + + if (isset($_COOKIE['cookiesAccepted']) && $_COOKIE['cookiesAccepted'] === 'true') { + return ['analytics' => true, 'marketing' => true, 'set' => true]; + } + + if (isset($_COOKIE['cookiesRejected']) && $_COOKIE['cookiesRejected'] === 'true') { + return ['analytics' => false, 'marketing' => false, 'set' => true]; + } + + return $defaults; +} + function fxCookiesConsentAccepted(): bool { - return isset($_COOKIE['cookiesAccepted']) && $_COOKIE['cookiesAccepted'] === 'true'; + $prefs = fxCookiesPreferences(); + + return $prefs['set'] && ($prefs['analytics'] || $prefs['marketing']); } function fxCookiesConsentRejected(): bool { - return isset($_COOKIE['cookiesRejected']) && $_COOKIE['cookiesRejected'] === 'true'; + $prefs = fxCookiesPreferences(); + + return $prefs['set'] && !$prefs['analytics'] && !$prefs['marketing']; } function fxCookiesConsentPending(): bool { - return !fxCookiesConsentAccepted() && !fxCookiesConsentRejected(); + return !fxCookiesPreferences()['set']; +} + +function fxCookiesAllowAnalytics(): bool +{ + return fxCookiesPreferences()['analytics']; } function fxCookiesAllowMarketing(): bool { - return fxCookiesConsentAccepted(); + return fxCookiesPreferences()['marketing']; } function fxCookiesConsentResetIfRequested(): void @@ -27,9 +63,12 @@ function fxCookiesConsentResetIfRequested(): void } $past = time() - 3600; - setcookie('cookiesAccepted', '', $past, '/'); - setcookie('cookiesRejected', '', $past, '/'); - unset($_COOKIE['cookiesAccepted'], $_COOKIE['cookiesRejected']); + $names = ['cookiesAccepted', 'cookiesRejected', 'cookiesPreferences']; + + foreach ($names as $name) { + setcookie($name, '', $past, '/'); + unset($_COOKIE[$name]); + } $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) ?: '/'; $query = []; @@ -49,12 +88,33 @@ function fxCookiesConsentResetIfRequested(): void exit; } -function fxCookieConsentText(string $clef, string $strLangue): string +function fxPageNeedsPaypal(): bool { - $defaults = [ + global $strPage; + + if (empty($strPage)) { + return false; + } + + static $paypalPages = [ + 'panier.php', + 'dons.php', + 'paiement_redirect.php', + 'panier', + 'cart', + 'don', + 'donate', + ]; + + return in_array($strPage, $paypalPages, true); +} + +function fxCookieConsentDefaults(): array +{ + return [ 'experience_cookies' => [ - 'fr' => 'Nous utilisons des témoins (cookies) pour assurer le bon fonctionnement du site et, avec votre consentement, mesurer l\'audience. Vous pouvez accepter ou refuser les témoins non essentiels sans affecter votre inscription.', - 'en' => 'We use cookies to ensure the site works properly and, with your consent, measure traffic. You may accept or refuse non-essential cookies without affecting your registration.', + 'fr' => 'Ce site utilise des témoins essentiels au fonctionnement de vos inscriptions. Avec votre permission, nous utilisons aussi des témoins de mesure d\'audience et de marketing. Vous pouvez accepter, refuser ou personnaliser vos choix.', + 'en' => 'This site uses essential cookies for registration. With your permission, we also use analytics and marketing cookies. You may accept, refuse or customize your choices.', ], 'bouton_acceper_cookies' => [ 'fr' => 'Tout accepter', @@ -64,12 +124,115 @@ function fxCookieConsentText(string $clef, string $strLangue): string 'fr' => 'Tout refuser', 'en' => 'Refuse all', ], + 'bouton_personnaliser_cookies' => [ + 'fr' => 'Personnaliser', + 'en' => 'Customize', + ], + 'bouton_enregistrer_cookies' => [ + 'fr' => 'Enregistrer mes choix', + 'en' => 'Save my choices', + ], + 'cookie_pref_titre' => [ + 'fr' => 'Préférences de témoins', + 'en' => 'Cookie preferences', + ], + 'cookie_pref_essentiels' => [ + 'fr' => 'Essentiels — requis pour le panier, la session et la sécurité. Toujours actifs.', + 'en' => 'Essential — required for cart, session and security. Always active.', + ], + 'cookie_pref_analytique' => [ + 'fr' => 'Mesure d\'audience — statistiques anonymes de fréquentation (Google Analytics).', + 'en' => 'Analytics — anonymous traffic statistics (Google Analytics).', + ], + 'cookie_pref_marketing' => [ + 'fr' => 'Marketing — mesure publicitaire et remarketing (Facebook / Meta, scripts événement).', + 'en' => 'Marketing — advertising measurement and remarketing (Facebook / Meta, event scripts).', + ], 'politique_cookie' => [ 'fr' => 'Politique de témoins', 'en' => 'Cookie policy', ], + 'politique_confidentialite' => [ + 'fr' => 'Politique de confidentialité', + 'en' => 'Privacy policy', + ], ]; +} +function fxCookieConsentTextIsDraft(string $clef, string $text): bool +{ + $text = trim($text); + + if ($text === '' || $text === $clef || $text === '*' . $clef . '*') { + return true; + } + + if (stripos($text, $clef) === 0) { + return true; + } + + return false; +} + +function fxEnsureCookieConsentTexts(): void +{ + global $objDatabase; + + if (!isset($objDatabase)) { + return; + } + + static $done = false; + + if ($done) { + return; + } + + $done = true; + + foreach (fxCookieConsentDefaults() as $clef => $texts) { + foreach (['fr', 'en'] as $lang) { + if (!isset($texts[$lang])) { + continue; + } + + $sql = "SELECT pk_info, info_texte FROM info + WHERE info_clef = '" . $objDatabase->fxEscape($clef) . "' + AND info_langue = '" . $objDatabase->fxEscape($lang) . "' + AND info_prg = 'global' + LIMIT 1"; + $row = $objDatabase->fxGetRow($sql); + + if (!$row) { + if (function_exists('fxSetInfo')) { + fxSetInfo('add', [ + 'info_clef' => $clef, + 'info_langue' => $lang, + 'info_texte' => $texts[$lang], + 'info_aide' => '', + 'info_prg' => 'global', + 'info_actif' => 1, + ], $lang); + } + + continue; + } + + if (!fxCookieConsentTextIsDraft($clef, (string) $row['info_texte'])) { + continue; + } + + $objDatabase->fxQuery( + "UPDATE info SET info_texte = '" . $objDatabase->fxEscape($texts[$lang]) . "', info_maj = NOW() + WHERE pk_info = " . intval($row['pk_info']) + ); + } + } +} + +function fxCookieConsentText(string $clef, string $strLangue): string +{ + $defaults = fxCookieConsentDefaults(); $lang = ($strLangue === 'en') ? 'en' : 'fr'; $fallback = $defaults[$clef][$lang] ?? $clef; @@ -77,11 +240,258 @@ function fxCookieConsentText(string $clef, string $strLangue): string return $fallback; } - $text = afficheTexte($clef, 0, 1, 1); + $text = trim((string) afficheTexte($clef, 0, 1, 1)); - if ($text === '' || $text === $clef || $text === '*' . $clef . '*') { + if (fxCookieConsentTextIsDraft($clef, $text)) { return $fallback; } return $text; } + +function fxCookieLegalPlaceholders(string $html): string +{ + global $vClient, $vDomaine; + + $org = htmlspecialchars((string) $vClient, ENT_QUOTES, 'UTF-8'); + $domain = htmlspecialchars((string) $vDomaine, ENT_QUOTES, 'UTF-8'); + $email = 'confidentialite@' . preg_replace('/^https?:\/\//', '', (string) parse_url($vDomaine, PHP_URL_HOST)); + $date = date('Y-m-d'); + + $replacements = [ + '[ORGANISATION]' => $org, + '[ORGANIZATION]' => $org, + '[SITE]' => $domain, + '[DATE]' => $date, + '[COURRIEL]' => $email, + '[EMAIL]' => $email, + ]; + + return str_replace(array_keys($replacements), array_values($replacements), $html); +} + +function fxCookiePolicyHtml(string $strLangue): string +{ + if ($strLangue === 'en') { + return fxCookieLegalPlaceholders(<<<'HTML' +

Cookie policy

+

Last updated: [DATE]

+

[ORGANIZATION] uses cookies on its registration site operated via MS1 Inscription (Progiweb inc.).

+

1. Essential cookies (always active)

+ +

2. Analytics cookies (optional)

+

Google Analytics (_ga, _gid) — only if you enable analytics in the banner.

+

3. Marketing cookies (optional)

+

Meta Pixel (_fbp) and event-specific scripts — only if you enable marketing in the banner.

+

4. Payment cookies

+

PayPal cookies may be placed during checkout only.

+

5. Your rights

+

Contact: [EMAIL]. You may reset your choices anytime using the cookie icon in the footer or by adding ?cookies=1 to the URL.

+

See also our privacy policy.

+HTML + ); + } + + return fxCookieLegalPlaceholders(<<<'HTML' +

Politique de témoins

+

Dernière mise à jour : [DATE]

+

[ORGANISATION] utilise des témoins sur son site d'inscription exploité via MS1 Inscription (Progiweb inc.).

+

1. Témoins essentiels (toujours actifs)

+ +

2. Témoins analytiques (optionnels)

+

Google Analytics (_ga, _gid) — uniquement si vous activez la mesure d'audience dans le bandeau.

+

3. Témoins marketing (optionnels)

+

Pixel Meta (_fbp) et scripts spécifiques à l'événement — uniquement si vous activez le marketing dans le bandeau.

+

4. Témoins de paiement

+

Des témoins PayPal peuvent être déposés lors du paiement seulement.

+

5. Vos droits

+

Contact : [COURRIEL]. Vous pouvez réinitialiser vos choix via l'icône témoins du pied de page ou en ajoutant ?cookies=1 à l'adresse.

+

Voir aussi notre politique de confidentialité.

+HTML + ); +} + +function fxPrivacyPolicyHtml(string $strLangue): string +{ + if ($strLangue === 'en') { + return fxCookieLegalPlaceholders(<<<'HTML' +

Privacy policy

+

Last updated: [DATE]

+

[ORGANIZATION] collects personal information when you register for events on [SITE], in compliance with Quebec Law 25.

+

Information collected

+

Name, contact details, date of birth, registration choices, payment metadata (processed by PayPal — we do not store full card numbers), and technical data (IP, browser).

+

Purposes

+ +

Third parties

+

Progiweb (MS1 Inscription), PayPal, Google (reCAPTCHA, Analytics if consented), Meta (Pixel if consented), and the event organizer.

+

Cross-border transfers

+

Some providers may process data outside Quebec. Contractual safeguards are applied.

+

Your rights

+

Access, correction, deletion and withdrawal of consent: [EMAIL]. Response within 30 days when possible.

+

Privacy officer

+

Contact: [EMAIL]

+

Cookie policy

+HTML + ); + } + + return fxCookieLegalPlaceholders(<<<'HTML' +

Politique de confidentialité

+

Dernière mise à jour : [DATE]

+

[ORGANISATION] collecte des renseignements personnels lors de vos inscriptions sur [SITE], conformément à la Loi 25.

+

Renseignements collectés

+

Nom, coordonnées, date de naissance, choix d'inscription, métadonnées de paiement (PayPal — aucun numéro de carte complet conservé), et données techniques (IP, navigateur).

+

Finalités

+ +

Tiers

+

Progiweb (MS1 Inscription), PayPal, Google (reCAPTCHA, Analytics si consenti), Meta (Pixel si consenti), et l'organisateur de l'événement.

+

Transferts hors Québec

+

Certains mandataires peuvent traiter des données à l'extérieur du Québec. Des mesures contractuelles s'appliquent.

+

Vos droits

+

Accès, rectification, suppression et retrait du consentement : [COURRIEL]. Réponse dans un délai de 30 jours lorsque possible.

+

Responsable de la protection des renseignements personnels

+

Contact : [COURRIEL]

+

Politique de témoins

+HTML + ); +} + +function fxEnsureLegalPage(string $code, string $labelFr, string $labelEn, string $titleFr, string $titleEn, callable $bodyFr, callable $bodyEn, int $sort = 90): void +{ + global $objDatabase; + + if (!isset($objDatabase)) { + return; + } + + $sql = "SELECT * FROM inscriptions_pages WHERE pag_code = '" . $objDatabase->fxEscape($code) . "' LIMIT 1"; + $row = $objDatabase->fxGetRow($sql); + + $textFr = $bodyFr(); + $textEn = $bodyEn(); + + if (!$row) { + $sqlInsert = "INSERT INTO inscriptions_pages + (pag_code, pag_label_fr, pag_label_en, pag_titre_fr, pag_titre_en, pag_texte_fr, pag_texte_en, pag_tri, pag_maj, pag_menu, pag_actif) + VALUES ( + '" . $objDatabase->fxEscape($code) . "', + '" . $objDatabase->fxEscape($labelFr) . "', + '" . $objDatabase->fxEscape($labelEn) . "', + '" . $objDatabase->fxEscape($titleFr) . "', + '" . $objDatabase->fxEscape($titleEn) . "', + '" . $objDatabase->fxEscape($textFr) . "', + '" . $objDatabase->fxEscape($textEn) . "', + " . intval($sort) . ", + NOW(), + 1, + 1 + )"; + $objDatabase->fxQuery($sqlInsert); + + return; + } + + $updates = []; + + if (trim((string) $row['pag_texte_fr']) === '') { + $updates[] = "pag_texte_fr = '" . $objDatabase->fxEscape($textFr) . "'"; + } + + if (trim((string) $row['pag_texte_en']) === '') { + $updates[] = "pag_texte_en = '" . $objDatabase->fxEscape($textEn) . "'"; + } + + if (trim((string) $row['pag_titre_fr']) === '') { + $updates[] = "pag_titre_fr = '" . $objDatabase->fxEscape($titleFr) . "'"; + } + + if (trim((string) $row['pag_titre_en']) === '') { + $updates[] = "pag_titre_en = '" . $objDatabase->fxEscape($titleEn) . "'"; + } + + if ((int) $row['pag_menu'] !== 1) { + $updates[] = 'pag_menu = 1'; + } + + if ((int) $row['pag_actif'] !== 1) { + $updates[] = 'pag_actif = 1'; + } + + if ($updates) { + $updates[] = 'pag_maj = NOW()'; + $objDatabase->fxQuery('UPDATE inscriptions_pages SET ' . implode(', ', $updates) . " WHERE pag_id = " . intval($row['pag_id'])); + } +} + +function fxEnsureLegalPages(): void +{ + static $done = false; + + if ($done) { + return; + } + + $done = true; + + fxEnsureCookieConsentTexts(); + + fxEnsureLegalPage( + 'cookie', + 'Témoins', + 'Cookies', + 'Politique de témoins', + 'Cookie policy', + static function () { + return fxCookiePolicyHtml('fr'); + }, + static function () { + return fxCookiePolicyHtml('en'); + }, + 98 + ); + + fxEnsureLegalPage( + 'confidentialite', + 'Confidentialité', + 'Privacy', + 'Politique de confidentialité', + 'Privacy policy', + static function () { + return fxPrivacyPolicyHtml('fr'); + }, + static function () { + return fxPrivacyPolicyHtml('en'); + }, + 99 + ); +} + +function fxCookiePolicyPageContent(string $strLangue): string +{ + return fxCookiePolicyHtml($strLangue); +} + +function fxPrivacyPolicyPageContent(string $strLangue): string +{ + return fxPrivacyPolicyHtml($strLangue); +} diff --git a/php/inc_fonctions.php b/php/inc_fonctions.php index 9c6dcb9..d30cccc 100644 --- a/php/inc_fonctions.php +++ b/php/inc_fonctions.php @@ -24,6 +24,8 @@ $objDatabase = new clsMysql($arrConDatabaseMain); $objDatabaseProd= new clsMysql($arrConDatabaseProd); $objDatabasePreProd= new clsMysql($arrConDatabasePreProd); +fxEnsureLegalPages(); + //MSIN-3999 if (isset($_GET['preprod'])) { $preprod = $_GET['preprod']; @@ -320,6 +322,10 @@ function fxGetPage($code) $sqlPage = "SELECT * FROM inscriptions_pages WHERE pag_actif = 1 AND pag_code = '" . $objDatabase->fxEscape($code) . "'"; $recPage = $objDatabase->fxGetRow($sqlPage); + if (!$recPage) { + return []; + } + $recPage['pag_description_fr'] = fxGetNbWords($recPage['pag_texte_fr'], 50); $recPage['pag_description_en'] = fxGetNbWords($recPage['pag_texte_en'], 50); diff --git a/php/inc_maintenance.php b/php/inc_maintenance.php index 04ae545..4a7dbe1 100644 --- a/php/inc_maintenance.php +++ b/php/inc_maintenance.php @@ -114,7 +114,7 @@ if ($tabMaintenance != null) { -