forked from HyphaApp/hypha
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Cookie popup improvements (HyphaApp#3976)
Fixes HyphaApp#3925. - Cookie consent preference is now stored in local storage rather than using cookies - Improves on the existing cookie banner by adding a `Learn More` option where users can read more about the essential & analytic cookies, and toggle analytics specifically. - If analytics are not used, the user is no longer given the option to opt out, as Hypha only uses essential cookies by default. - Added a link/button to the footer default that allows the user to re-open the cookie prompt without having to clear it from their local storage.
- Loading branch information
Showing
9 changed files
with
381 additions
and
49 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
Hypha comes stock with a cookie banner indicating that only essential cookies are used. You can configure this banner to display information about analytics cookies in Wagtail Admin under `Settings` -> `Cookie banner settings`. | ||
|
||
It's possible to configure settings such as: | ||
|
||
* Edit General cookie consent message | ||
* Edit Essential cookies informational statement | ||
* Enable & edit the analytics cookies informational statement | ||
|
||
### Retrieving preferences | ||
|
||
Cookie preferences are stored in the browser's local storage using the key `cookieconsent`, and can be globally retrieved via: | ||
|
||
```js | ||
localStorage.get('cookieconsent') | ||
``` | ||
|
||
There are three valid values `cookieconsent` in local storage: | ||
|
||
* `decline` - analytics cookies have been declined, only essential cookies should be used | ||
* `accept` - all cookies are consented to | ||
* `ack` - there are no analytics cookies and user accepts that only essential cookies are in use | ||
* **null** - no selection has been made by the user in the cookie banner | ||
|
||
On page load the cookie banner JavaScript snippet will check if cookie policies have changed (ie. the site originally only used essential cookies, user ack'd, then analytics cookies were enabled) and automatically reprompt the user with the new cookie options. | ||
|
||
### Allowing the user to change cookie preferences | ||
|
||
The functions to open and close the cookie consent prompts are globally exposed in the JavaScript and can be utilized via: | ||
|
||
```js | ||
// Open consent prompt | ||
window.openConsentPrompt() | ||
|
||
// Close consent prompt | ||
window.closeConsentPrompt() | ||
``` | ||
|
||
By default, there is a button in the footer that allows the user to re-open the cookie consent prompt: | ||
|
||
```html | ||
<a href="#" onclick="openConsentPrompt()">Cookie Settings</a> | ||
``` | ||
|
||
This can be further configured in Wagtail Admin under `Settings` -> `System settings` -> `Footer content`. |
65 changes: 65 additions & 0 deletions
65
hypha/cookieconsent/migrations/0003_alter_cookieconsentsettings_options_and_more.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
# Generated by Django 4.2.11 on 2024-06-04 19:41 | ||
|
||
from django.db import migrations, models | ||
import wagtail.fields | ||
|
||
|
||
class Migration(migrations.Migration): | ||
dependencies = [ | ||
("cookieconsent", "0002_remove_cookieconsentsettings_site_and_more"), | ||
] | ||
|
||
operations = [ | ||
migrations.AlterModelOptions( | ||
name="cookieconsentsettings", | ||
options={"verbose_name": "Cookie banner settings"}, | ||
), | ||
migrations.AddField( | ||
model_name="cookieconsentsettings", | ||
name="cookieconsent_analytics", | ||
field=models.BooleanField( | ||
default=False, | ||
verbose_name="Include consent option for analytics cookies", | ||
), | ||
), | ||
migrations.AddField( | ||
model_name="cookieconsentsettings", | ||
name="cookieconsent_analytics_about", | ||
field=wagtail.fields.RichTextField( | ||
default="<p>With these cookies we count visits and traffic sources to help improve the performance of our services through metrics. These cookies show us which pages on our services are the most and the least popular, and how users navigate our services. The information collected is aggregated and contains no personally identifiable information. If you block these cookies, then we will not know when you have used our services.</p>", | ||
verbose_name='Analytics cookies information to be displayed under "Learn More"', | ||
), | ||
), | ||
migrations.AddField( | ||
model_name="cookieconsentsettings", | ||
name="cookieconsent_essential_about", | ||
field=wagtail.fields.RichTextField( | ||
default="<p>Strictly necessary for the operation of a website because they enable you to navigate around the site and use features. These cookies cannot be switched off in our systems and do not store any personally identifiable information.</p>", | ||
verbose_name='Essential cookies information to be displayed under "Learn More"', | ||
), | ||
), | ||
migrations.AlterField( | ||
model_name="cookieconsentsettings", | ||
name="cookieconsent_active", | ||
field=models.BooleanField( | ||
default=False, verbose_name="Activate cookie pop-up banner" | ||
), | ||
), | ||
migrations.AlterField( | ||
model_name="cookieconsentsettings", | ||
name="cookieconsent_message", | ||
field=wagtail.fields.RichTextField( | ||
default='<p>This website deploys cookies for basic functionality and to keep it secure. These cookies are strictly necessary. Optional analysis cookies which provide us with statistical information about the use of the website may also be deployed, but only with your consent. Please review our <a href="/data-privacy-policy/">Privacy & Data Policy</a> for more information.</p>', | ||
verbose_name="Cookie consent message", | ||
), | ||
), | ||
migrations.AlterField( | ||
model_name="cookieconsentsettings", | ||
name="cookieconsent_title", | ||
field=models.CharField( | ||
default="Your cookie settings", | ||
max_length=255, | ||
verbose_name="Cookie banner title", | ||
), | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,38 +1,98 @@ | ||
(function () { | ||
"use strict"; | ||
|
||
if (typeof Cookies !== "undefined") { | ||
const cookieconsent = document.querySelector(".cookieconsent"); | ||
|
||
if ( | ||
typeof Cookies.get("cookieconsent") === "undefined" && | ||
cookieconsent | ||
) { | ||
cookieconsent.classList.add("js-cookieconsent-open"); | ||
// Used when an analytics cookie notice is enabled | ||
const ACCEPT = "accept"; | ||
const DECLINE = "decline"; | ||
const ACK = "ack"; // Only for essential cookies | ||
|
||
// Constant key used for localstorage | ||
const COOKIECONSENT_KEY = "cookieconsent"; | ||
|
||
// Class constants | ||
const CLASS_COOKIECONSENT = "cookieconsent"; | ||
const CLASS_LEARNMORE = "cookieconsent__learnmore"; | ||
const CLASS_COOKIEBRIEF = "cookieconsent__brief"; | ||
const CLASS_COOKIECONTENT = "cookieconsent__content"; | ||
const CLASS_JS_CONSENT_OPEN = "js-cookieconsent-open"; | ||
const CLASS_JS_LEARNMORE = "js-cookieconsent-show-learnmore"; | ||
const CLASS_JS_LEARNMORE_EXPAND = `${CLASS_JS_LEARNMORE}-expand`; | ||
|
||
const cookieconsent = document.querySelector(`.${CLASS_COOKIECONSENT}`); | ||
if (!cookieconsent) return; | ||
|
||
const cookieButtons = cookieconsent.querySelectorAll( | ||
"button[data-consent]" | ||
); | ||
const learnMoreToggles = cookieconsent.querySelectorAll( | ||
".button--learn-more" | ||
); | ||
|
||
function getConsentValue() { | ||
return localStorage.getItem(COOKIECONSENT_KEY); | ||
} | ||
|
||
function setConsentValue(value) { | ||
if ([ACCEPT, DECLINE, ACK].includes(value)) { | ||
localStorage.setItem(COOKIECONSENT_KEY, value); | ||
} else { | ||
// If for whatever reason the value is not in the predefined values, assume decline | ||
localStorage.setItem(COOKIECONSENT_KEY, DECLINE); | ||
} | ||
} | ||
|
||
const cookie_buttons = Array.prototype.slice.call( | ||
document.querySelectorAll("button[data-consent]") | ||
); | ||
const sitedomain = window.location.hostname.split(".").slice(-2); | ||
const cookiedomain = sitedomain.join("."); | ||
let cookie_options = []; | ||
cookie_options["domain"] = cookiedomain; | ||
cookie_options["sameSite"] = "strict"; | ||
cookie_options["expires"] = 365; | ||
if (window.location.protocol === "https:") { | ||
cookie_options["secure"] = true; | ||
function openConsentPrompt() { | ||
cookieconsent.classList.add(CLASS_JS_CONSENT_OPEN); | ||
} | ||
|
||
function closeConsentPrompt() { | ||
cookieconsent.classList.remove(CLASS_JS_CONSENT_OPEN); | ||
} | ||
|
||
// Expose consent prompt opening/closing globally (ie. to use in a footer) | ||
window.openConsentPrompt = openConsentPrompt; | ||
window.closeConsentPrompt = closeConsentPrompt; | ||
|
||
function toggleLearnMore(open) { | ||
const content = cookieconsent.querySelector(`.${CLASS_COOKIECONTENT}`); | ||
if (open) { | ||
content.classList.add(CLASS_JS_LEARNMORE); | ||
cookieconsent.classList.add(CLASS_JS_LEARNMORE_EXPAND); | ||
} else { | ||
content.classList.remove(CLASS_JS_LEARNMORE); | ||
cookieconsent.classList.remove(CLASS_JS_LEARNMORE_EXPAND); | ||
} | ||
setInputTabIndex(`.${CLASS_LEARNMORE}`, open ? 0 : -1); | ||
setInputTabIndex(`.${CLASS_COOKIEBRIEF}`, open ? -1 : 0); | ||
} | ||
|
||
cookie_buttons.forEach(function (button) { | ||
button.addEventListener("click", function () { | ||
if (button.getAttribute("data-consent") == "true") { | ||
Cookies.set("cookieconsent", "accept", cookie_options); | ||
} else { | ||
Cookies.set("cookieconsent", "decline", cookie_options); | ||
} | ||
cookieconsent.classList.remove("js-cookieconsent-open"); | ||
}); | ||
}); | ||
// Adds "tabability" to menu buttons/toggles | ||
function setInputTabIndex(wrapperClassSelector, tabValue) { | ||
const wrapper = cookieconsent.querySelector(wrapperClassSelector); | ||
const tabables = wrapper.querySelectorAll("button, input"); | ||
tabables.forEach((element) => (element.tabIndex = tabValue)); | ||
} | ||
|
||
// Open the prompt if consent value is undefined OR if analytics has been added since the user ack'd essential cookies | ||
if ( | ||
getConsentValue() == undefined || | ||
(getConsentValue() === ACK && cookieButtons.length > 1) | ||
) { | ||
openConsentPrompt(); | ||
} | ||
|
||
cookieButtons.forEach(function (button) { | ||
button.addEventListener("click", function () { | ||
const buttonValue = button.getAttribute("data-consent"); | ||
setConsentValue(buttonValue); | ||
closeConsentPrompt(); | ||
}); | ||
}); | ||
|
||
learnMoreToggles.forEach(function (button) { | ||
button.addEventListener("click", function () { | ||
const buttonValue = button.getAttribute("show-learn-more"); | ||
toggleLearnMore(buttonValue === "true"); | ||
}); | ||
}); | ||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
hypha/core/migrations/0005_alter_systemsettings_footer_content.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# Generated by Django 4.2.11 on 2024-06-06 21:08 | ||
|
||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
dependencies = [ | ||
("core", "0004_move_system_settings_data"), | ||
] | ||
|
||
operations = [ | ||
migrations.AlterField( | ||
model_name="systemsettings", | ||
name="footer_content", | ||
field=models.TextField( | ||
blank=True, | ||
default='<p>Configure this text in Wagtail admin -> Settings -> System settings.</p>\n<br>\n<a href="#" onclick="openConsentPrompt()">Cookie Settings</a>', | ||
help_text="This will be added to the footer, html tags is allowed.", | ||
verbose_name="Footer content", | ||
), | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.