From 49bfb9ff39b8b0a652a2bfdaa93ffb2c5d3b3a8c Mon Sep 17 00:00:00 2001 From: Matt Blenkinsop Date: Mon, 11 Jan 2021 14:50:17 +0000 Subject: [PATCH] Bug 27378: Introduce cookie consent to OPAC and staff client To avoid confusion around commit messages and the content of this enhancement, this first commit is a squashed commit of all the original code submited to this bug. Following a few years of inactivity, it has been rebased and re-submitted with some fixes and concept changes contained in the more recent commits. Signed-ff-by: Barry Cannon Signed-off-by: Katrin Fischer Bug 27378: (follow-up) Add missing filters (cherry picked from commit 6b8565b949b62269f6d850e6d412458d0dbcfb37) Signed-off-by: Katrin Fischer Bug 27378: Fix accessibility issues Signed-off-by: Katrin Fischer Bug 27378: Update to new atomicupdate structure This patch consolidates the previous 4 database update files into one atomicupdate file in line with the new structure Signed-off-by: Katrin Fischer Bug 27378: Change ConsentJS to CookieConsentedJS This patch updates the name of the ConsentJS syspref to CookieConsentedJS and amends the description to be more clear what the syspref is for Signed-off-by: Katrin Fischer Bug 27378: Stop the codemirror editor and delete confirmation from duplicating Previously, if the "Add new code button" was clicked in the CookieConsentedJS editor, the original entry would have duplicated CodeMirror editors. This was exponential, i.e adding two new lines would result in three codemirror editors appearing on the first entry, two on the second and so on. The click event was not being applied properly and was being applied to every element with the .expand-textarea class, rather than specifically the new elements being created. The addExpandHandler function now loops through each element individually and decides whether to apply the click event handler. Similarly, the delete confirmation was dupliacting for the same reason. This has also been resolved. Signed-off-by: Katrin Fischer Bug 27378: Remove two sysprefs and replace with html customisations Currently there are two sysprefs - CookieConsentBar and CookieConsentPopup. These allow the user to select what text they would like to see in the consent bar and modal. These have been removed and replaced with HTML customisations to allow more flexible customisations and different languages. Sponsored by: PTFS-Europe Signed-off-by: Katrin Fischer Bug 27378: (QA follow-up) Small fixes and tidy-ups This patch does the following: - Realphabetizes the lines in sysprefs.sql - Fixes a formatting error in patrons.pref - Adjusts the position of the cookie consent bar if the language selector is visible - Fixes translatability on the syspref modal Signed-off-by: Katrin Fischer Bug 27378: (QA follow-up) Allow staff to view their cookie consents This patch allows staff to view their cookie consents through a link in the dropdown menu in the navbar. Previously staff had no way of accessing their cookie consents Signed-off-by: Katrin Fischer Bug 27378: (QA follow-up) Add cancel button to cookie modal This patch adds a cancel button to the modal for reviewing cookie consents. Previously there was no way to exit without selecting one of the cookie options Signed-off-by: Katrin Fischer Bug 27378: (QA follow-up) Add filtering for OPAC only and staff only cookies This patch fixes an issue where cookies selected as OPAC only would still show in the staff client and vise versa. The cookies are now filtered and only the correct cookies will be used in the OPAC and staff client Signed-off-by: Katrin Fischer Bug 27378: (QA follow-up) Fix tests and character encoding This patch fixes an encoding issue when using diacritics. It also fixes a failing test, corrects the format of the "Cancel" links in the modal and perltidy has been used on all relevant files Signed-off-by: Katrin Fischer Signed-off-by: Tomas Cohen Arazi --- Koha/AdditionalContents.pm | 2 + Koha/Template/Plugin/JSConsents.pm | 69 ++++ admin/preferences.pl | 17 +- .../bug_27378-add_cookie_consents.pl | 20 + installer/data/mysql/mandatory/sysprefs.sql | 2 + .../intranet-tmpl/prog/css/preferences.css | 9 +- .../prog/css/src/staff-global.scss | 91 +++++ .../intranet-tmpl/prog/en/includes/header.inc | 8 + .../prog/en/includes/intranet-bottom.inc | 81 ++++ .../prog/en/modules/admin/preferences.tt | 2 + .../en/modules/admin/preferences/patrons.pref | 12 + .../en/modules/tools/additional-contents.tt | 2 +- .../intranet-tmpl/prog/js/cookieconsent.js | 195 +++++++++ .../prog/js/pages/preferences.js | 376 ++++++++++++++++-- .../opac-tmpl/bootstrap/css/src/opac.scss | 58 +++ .../bootstrap/en/includes/masthead.inc | 70 ++++ .../bootstrap/en/includes/opac-bottom.inc | 14 + .../bootstrap/en/includes/usermenu.inc | 2 +- .../en/modules/opac-patron-consent.tt | 5 + .../opac-tmpl/bootstrap/js/cookieconsent.js | 211 ++++++++++ .../Koha/Template/Plugin/JSConsents.t | 45 +++ 21 files changed, 1236 insertions(+), 55 deletions(-) create mode 100644 Koha/Template/Plugin/JSConsents.pm create mode 100644 installer/data/mysql/atomicupdate/bug_27378-add_cookie_consents.pl create mode 100644 koha-tmpl/intranet-tmpl/prog/js/cookieconsent.js create mode 100644 koha-tmpl/opac-tmpl/bootstrap/js/cookieconsent.js create mode 100755 t/db_dependent/Koha/Template/Plugin/JSConsents.t diff --git a/Koha/AdditionalContents.pm b/Koha/AdditionalContents.pm index 3ed9f42f41..430ff53934 100644 --- a/Koha/AdditionalContents.pm +++ b/Koha/AdditionalContents.pm @@ -63,6 +63,8 @@ location is one of this: - OpacLoginInstructions - OpacSuggestionInstructions - ArticleRequestsDisclaimerText +- CookieConsentBar +- CookieConsentPopup =cut diff --git a/Koha/Template/Plugin/JSConsents.pm b/Koha/Template/Plugin/JSConsents.pm new file mode 100644 index 0000000000..174a36a02e --- /dev/null +++ b/Koha/Template/Plugin/JSConsents.pm @@ -0,0 +1,69 @@ +package Koha::Template::Plugin::JSConsents; + +# Copyright 2021 PTFS Europe +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use Template::Plugin; +use base qw( Template::Plugin ); +use MIME::Base64 qw{ decode_base64 }; +use JSON qw{ decode_json }; +use Encode qw{ encode_utf8 }; + +use C4::Context; + +sub all { + my ( $self, $filter ) = @_; + + my $consents = C4::Context->preference('CookieConsentedJS'); + if ( length $consents > 0 ) { + my $decoded_consents = decode_base64($consents); + my $convert_to_utf8 = encode_utf8($decoded_consents); + my $consents_array = decode_json $convert_to_utf8; + my @filtered_consents = grep { $_->{$filter} } @{$consents_array}; + return \@filtered_consents; + } else { + return []; + } +} + +1; + +=head1 NAME + +Koha::Template::Plugin::JSConsents - TT Plugin for Javascript consents +Provided by CookieConsentedJS syspref + +=head1 SYNOPSIS + +[% USE JSConsents %] + +[% JSConsents.all() %] + +=head1 ROUTINES + +=head2 all + +In a template, you can get the all Javascript snippets +that require consent using the following TT: +[% JSConsents.all() %] +The consents are returned in an array of hashes + +=head1 AUTHOR + +Andrew Isherwood + +=cut diff --git a/admin/preferences.pl b/admin/preferences.pl index 45a5891c62..73b8c442e3 100755 --- a/admin/preferences.pl +++ b/admin/preferences.pl @@ -59,12 +59,17 @@ sub _get_chunk { if( $options{'syntax'} ){ $chunk->{'syntax'} = $options{'syntax'}; } - - if( $options{'type'} && $options{'type'} eq 'modalselect' ){ - $chunk->{'source'} = $options{'source'}; - $chunk->{'exclusions'} = $options{'exclusions'} // ""; - $chunk->{'required'} = $options{'required'} // ""; - $chunk->{'type'} = 'modalselect'; + if( $options{'type'} ) { + if( $options{'type'} eq 'modalselect' ){ + $chunk->{'source'} = $options{'source'}; + $chunk->{'exclusions'} = $options{'exclusions'} // ""; + $chunk->{'required'} = $options{'required'} // ""; + $chunk->{'type'} = 'modalselect'; + } elsif( $options{'type'} eq 'modaljs' ){ + $chunk->{'type'} = 'modaljs'; + $chunk->{'initiator'} = $options{'initiator'}; + $chunk->{'processor'} = $options{'processor'}; + } } if ( $options{'class'} && $options{'class'} eq 'password' ) { diff --git a/installer/data/mysql/atomicupdate/bug_27378-add_cookie_consents.pl b/installer/data/mysql/atomicupdate/bug_27378-add_cookie_consents.pl new file mode 100644 index 0000000000..40f2904985 --- /dev/null +++ b/installer/data/mysql/atomicupdate/bug_27378-add_cookie_consents.pl @@ -0,0 +1,20 @@ +use Modern::Perl; + +return { + bug_number => "27378", + description => "Adds the sysprefs for cookie consents", + up => sub { + my ($args) = @_; + my ( $dbh, $out ) = @$args{qw(dbh out)}; + + $dbh->do( + q| INSERT IGNORE INTO systempreferences (variable, value, explanation, options, type) VALUES ('CookieConsentedJS', '', 'Add Javascript code that will run if cookie consent is provided (e.g. tracking code).', '', 'Free'); | + ); + say $out "Added new system preference 'CookieConsentedJS'"; + + $dbh->do( + q| INSERT IGNORE INTO systempreferences (variable, value, explanation, options, type) VALUES ('CookieConsent', '0', 'Require cookie consent to be displayed', '', 'YesNo'); | + ); + say $out "Added new system preference 'CookieConsent'"; + }, +}; diff --git a/installer/data/mysql/mandatory/sysprefs.sql b/installer/data/mysql/mandatory/sysprefs.sql index aa8a526e38..fad32d896e 100644 --- a/installer/data/mysql/mandatory/sysprefs.sql +++ b/installer/data/mysql/mandatory/sysprefs.sql @@ -158,6 +158,8 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` ('ConfirmFutureHolds','0','','Number of days for confirming future holds','Integer'), ('ConsiderOnSiteCheckoutsAsNormalCheckouts','1',NULL,'Consider on-site checkouts as normal checkouts','YesNo'), ('ContentWarningField', '', NULL, 'MARC field to use for content warnings', 'Free'), +('CookieConsent', '0', NULL, 'Require cookie consent to be displayed', 'YesNo'), +('CookieConsentedJS', '', NULL, 'Add Javascript code that will run if cookie consent is provided (e.g. tracking code).', 'Free'), ('CreateAVFromCataloguing', '1', '', 'Ability to create authorized values from the cataloguing module', 'YesNo'), ('CronjobLog','0',NULL,'If ON, log information from cron jobs.','YesNo'), ('CumulativeRestrictionPeriods',0,NULL,'Cumulate the restriction periods instead of keeping the highest','YesNo'), diff --git a/koha-tmpl/intranet-tmpl/prog/css/preferences.css b/koha-tmpl/intranet-tmpl/prog/css/preferences.css index ce15265389..d0f41db136 100644 --- a/koha-tmpl/intranet-tmpl/prog/css/preferences.css +++ b/koha-tmpl/intranet-tmpl/prog/css/preferences.css @@ -3,7 +3,8 @@ .preference-multi, .preference-long, .preference-file, -.preference-modalselect { +.preference-modalselect, +.preference-modaljs { width: 20em; } @@ -15,11 +16,13 @@ width: 5em; } -input[type="text"].modalselect { +input[type="text"].modalselect, +input[type="text"].modaljs { cursor: pointer; } -input[type="text"].modalselect:hover { +input[type="text"].modalselect:hover, +input[type="text"].modaljs:hover { background-color: #FFC; } diff --git a/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss b/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss index e25e4a5460..6a8f64e29e 100644 --- a/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss +++ b/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss @@ -2952,6 +2952,17 @@ button, } } + &.btn-info { + color: #fff; + background-color: #194EA3; + border-color: #0B439D; + } + + &.btn-success { + background-color: #419641; + border-color: #398339; + } + &.btn-danger { background-color: #CC3333; border-color: #9B2323; @@ -4302,6 +4313,86 @@ div .suggestion_note { } } +#cookieConsentedJSItems { + #cookieConsentedJSWarning { + margin: 20px 0; + text-align: center; + font-weight: 800; + font-size: 120%; + } + .cookieConsentedJSItem { + margin-bottom: 30px; + padding: 10px; + border: 1px solid #ccc; + } + .consentRow { + display: flex; + margin-bottom: 20px; + .consentItem { + margin-right: 40px; + } + .consentItem:last-child { + margin-right: 0; + } + } + .codeRow { + display: block; + } + .consentDelete { + display: block; + text-align: right; + } +} + +/* Cookie consent bar */ +#cookieConsentBar { + display: none; + padding: 20px; + position: fixed; + bottom: 0; + left: 0; + right: 0; + background: rgba(0,0,0,0.7); + color: rgba(255,255,255,0.9); + z-index: 1; + a:link, a:visited { + color: #d7ebff; + } + #consentButtons { + margin-top: 10px; + text-align: right; + } + button:not(:last-child) { + margin-right: 10px; + } +} + +/* Cookie consent modal */ +#cookieConsentModal { + .modal-dialog { + width: 700px; + } + .consentModalItem { + display: flex; + align-items: center; + border: 1px solid #ccc; + padding: 10px 20px; + border-radius: 5px; + } + .consentModalItem:not(:last-child) { + margin-bottom: 20px; + } + .consentItemCheckbox { + padding-right: 20px; + } + .consentItemName { + font-weight: bold; + } + #cookieConsentPopupText { + margin: 20px 0; + } +} + @import "header"; @import "toolbar"; @import "forms"; diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/header.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/header.inc index e4c6280285..0ab7ddbfdc 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/header.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/header.inc @@ -3,6 +3,9 @@ [% USE Desks %] [% USE Registers %] [% USE Koha %] +[% IF Koha.Preference( 'CookieConsent' ) %] + [% USE JSConsents %] +[% END %]