From a8f4b608c3d3ff296cc00d47ff6e04b234170ee2 Mon Sep 17 00:00:00 2001
From: Andrew Isherwood <andrew.isherwood@ptfs-europe.com>
Date: Wed, 20 Jan 2021 09:26:27 +0000
Subject: [PATCH] Bug 27378: Add cookie consent display to staff UI

This commit adds the display of the cookie consent bar and modal to the
staff interface:

- Adds a new cookieconsent.js script that drives the display and
functionality of the cookie consent bar and modal
- Adds styles for the cookie consent bar and modal
- Adds the creation and population of the cookie consent bar and modal,
if appropriate

Signed-ff-by: Barry Cannon <bc@interleaf.ie>
(cherry picked from commit 40d4b094420ea1495996976434d250b15ee78c1b)
---
 .../prog/css/src/staff-global.scss            |  49 ++++++
 .../prog/en/includes/intranet-bottom.inc      |  69 ++++++++
 .../intranet-tmpl/prog/js/cookieconsent.js    | 165 ++++++++++++++++++
 3 files changed, 283 insertions(+)
 create mode 100644 koha-tmpl/intranet-tmpl/prog/js/cookieconsent.js

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 6c0b7b9524..9f576aae56 100644
--- a/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss
+++ b/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss
@@ -4350,6 +4350,55 @@ div .suggestion_note {
     }
 }
 
+/* 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/intranet-bottom.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/intranet-bottom.inc
index 27ba415943..5a94f7d53a 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/includes/intranet-bottom.inc
+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/intranet-bottom.inc
@@ -1,6 +1,10 @@
 [% USE raw %]
 [% USE Koha %]
 [% USE KohaPlugins %]
+[% USE Asset %]
+[% IF Koha.Preference( 'CookieConsent' ) %]
+    [% USE JSConsents %]
+[% END %]
         </div>
 [% IF ( ( languages_loop ) && ( ! popup_window ) && ( Koha.Preference('StaffLangSelectorMode') == 'both' || Koha.Preference('StaffLangSelectorMode') == 'footer') ) %]
     [% UNLESS ( one_language_enabled ) %]
@@ -74,5 +78,70 @@
     [% END %]
 [% KohaPlugins.get_plugins_intranet_js | $raw %]
 
+    <!-- Cookie consent -->
+    [% IF Koha.Preference( 'CookieConsent' ) %]
+        <!-- Cookie consent bar -->
+        <div id="cookieConsentBar" aria-hidden="true">
+            [% Koha.Preference( 'CookieConsentBar' ) | $raw %]
+            <div id="consentButtons">
+                <button type="button" class="btn btn-primary consentAcceptAll">Accept all cookies</button>
+                [% IF ( Koha.Preference( 'ConsentJS' ).length > 0 ) %]
+                    <button type="button" class="btn btn-primary consentAcceptEssential">Accept only essential cookies</button>
+                    <button type="button" class="btn btn-info" id="consentMoreInfo">More information</button>
+                [% END %]
+            </div>
+        </div> <!-- /#cookieConsentBar -->
+        <!-- Cookie consent modal -->
+        <div id="cookieConsentModal" class="modal" tabindex="-1" role="dialog" aria-labelledby="cookieConsentModalLabel" aria-hidden="true">
+            <div class="modal-dialog">
+                <div class="modal-content">
+                    <div class="modal-header">
+                        <h2 class="modal-title" id="cookieConsentModalLabel">Cookie consent</h2>
+                    </div>
+                    <div class="modal-body">
+                        [% IF Koha.Preference( 'CookieConsentPopup' ) %]
+                            <div id="cookieConsentPopupText">
+                                [% Koha.Preference( 'CookieConsentPopup' ) | $raw %]
+                            </div>
+                        [% END %]
+                        <div id="consentCookieList">
+                            [% consents = JSConsents.all() %]
+                            [% FOREACH consent IN consents %]
+                                <div class="consentModalItem">
+                                    <div class="consentItemCheckbox">
+                                        <input class="consentCheckbox" type="checkbox" name="consentCheckbox" value="[% consent.id %]">
+                                    </div>
+                                    <div class="consentItemMeta">
+                                        <div class="consentItemName">[% consent.name | html %]</div>
+                                        <div class="consentItemDescription">[% consent.description | html %]</div>
+                                    </div>
+                                </div>
+                            [% END %]
+                        </div>
+                    </div>
+                    <div class="modal-footer">
+                        <div id="consentButtons">
+                            <button type="button" class="btn btn-primary consentAcceptAll">Accept all cookies</button>
+                            [% IF ( Koha.Preference( 'ConsentJS' ).length > 0 ) %]
+                                <button type="button" class="btn btn-primary consentAcceptEssential">Accept only essential cookies</button>
+                                <button type="button" class="btn btn-warning" id="consentAcceptSelected">Accept selected non-essential cookies</button>
+                            [% END %]
+                        </div>
+                    </div>
+                </div> <!-- /.modal-content -->
+            </div> <!-- /.modal-dialog -->
+        </div>  <!-- /#cookieConsentModal  -->
+    [% END %]
+    <!-- ConsentJS code that may run -->
+    [% IF Koha.Preference( 'CookieConsent' ) && Koha.Preference( 'ConsentJS' ).length > 0 %]
+        [% consents = JSConsents.all() %]
+        [% FOREACH consent IN consents %]
+            <div class="consentCode" style="display:none" aria-hidden="true" data-consent-id="[% consent.id %]" data-consent-code="[% consent.code %]" data-requires-consent="[% consent.staffConsent ? 'true' : 'false' %]"></div>
+        [% END %]
+    [% END %]
+    [% IF Koha.Preference( 'CookieConsent' ) %]
+        [% Asset.js("js/cookieconsent.js") | $raw %]
+    [% END %]
+
 </body>
 </html>
diff --git a/koha-tmpl/intranet-tmpl/prog/js/cookieconsent.js b/koha-tmpl/intranet-tmpl/prog/js/cookieconsent.js
new file mode 100644
index 0000000000..c718c73e39
--- /dev/null
+++ b/koha-tmpl/intranet-tmpl/prog/js/cookieconsent.js
@@ -0,0 +1,165 @@
+(function () {
+    // Has the user previously consented, establish our
+    // initial state
+    let selected = [];
+    let existingConsent = '';
+    // The presence of a 'cookieConsent' local storage item indicates
+    // that previous consent has been set. If the value is empty, then
+    // no consent to non-essential cookies was given
+    const hasStoredConsent = localStorage.getItem('cookieConsent');
+    getExistingConsent();
+
+    // The consent bar may not be in the DOM if it is not being used
+    const consentBar = $('#cookieConsentBar');
+    if (!consentBar) {
+        return;
+    }
+    if (hasStoredConsent === null) {
+        showConsentBar();
+    }
+    addButtonHandlers();
+
+    // When the modal is opened, populate our state based on currently
+    // selected values
+    $('#cookieConsentModal').on('shown.bs.modal', function () {
+        initialiseSelected();
+    });
+
+    // Initialise existing consent based on local storage
+    function getExistingConsent() {
+        existingConsent = localStorage.getItem('cookieConsent') ?
+            localStorage.getItem('cookieConsent') :
+            [];
+    }
+
+    function showConsentBar() {
+        const consentBar = $('#cookieConsentBar');
+        consentBar.attr('aria-hidden', 'false');
+        consentBar.show();
+    }
+
+    function hideConsentBar() {
+        const consentBar = $('#cookieConsentBar');
+        consentBar.attr('aria-hidden', 'true');
+        consentBar.hide();
+    }
+
+    // Hides the appropriate consent container, depending on what
+    // is currently visible
+    function hideContainer() {
+        if ($('#cookieConsentModal').hasClass('in')) {
+            $('#cookieConsentModal').modal('hide');
+        } else {
+            hideConsentBar();
+        }
+    }
+
+    // Initialise our state of selected item and enable/disable
+    // the "Accept selected" button appropriately
+    function initialiseSelected() {
+        selected = [];
+        $('.consentCheckbox').each(function () {
+            const val = $(this).val();
+            if (existingConsent.indexOf(val) > -1 ) {
+                $(this).prop('checked', true);
+                selected.push(val);
+            } else {
+                $(this).prop('checked', false);
+            }
+        });
+        enableDisableSelectedButton();
+    }
+
+    // Maintain our state of selected items and enable/disable
+    // the "Accept selected" button appropriately
+    function maintainSelected() {
+        selected = [];
+        $('.consentCheckbox:checked').each(function () {
+            selected.push($(this).val());
+        });
+        enableDisableSelectedButton();
+    }
+
+    // Set the enabled / disabled state of the
+    // "Accept selected button based on the checkbox
+    // states
+    function enableDisableSelectedButton() {
+        $('#consentAcceptSelected').prop(
+            'disabled',
+            selected.length === 0
+        );
+    }
+
+    function runConsentedCode() {
+        getExistingConsent();
+        $('.consentCode').each(function () {
+            // The user has explicitly consented to this code, or the
+            // code doesn't require consent in the staff view
+            if (
+                existingConsent.indexOf($(this).data('consent-id')) > -1 ||
+                !$(this).data('requires-consent')
+            ) {
+                const code = atob($(this).data('consent-code'));
+                const func = Function(code);
+                func();
+            }
+        });
+    }
+
+    function addButtonHandlers() {
+        // "Accept all" handler
+        $('.consentAcceptAll').on('click', function(e) {
+            e.preventDefault();
+            let toSave = [];
+            $('.consentCheckbox').each(function () {
+                const val = $(this).val();
+                toSave.push(val);
+            });
+            localStorage.setItem('cookieConsent', toSave);
+            hideContainer();
+            runConsentedCode();
+        });
+
+        // "Accept essential" handler
+        $('.consentAcceptEssential').on('click', function(e) {
+            e.preventDefault();
+            localStorage.setItem('cookieConsent', []);
+            hideContainer();
+        });
+
+        // "Accept selected" handler
+        $('#consentAcceptSelected').on('click', function(e) {
+            e.preventDefault();
+            const toSave = selected.length > 0 ? selected : [];
+            localStorage.setItem('cookieConsent', toSave);
+            hideContainer();
+            runConsentedCode();
+        });
+
+        // "More information" handler
+        $('#consentMoreInfo').on(
+            'click',
+            function (e) {
+                e.preventDefault();
+                hideConsentBar();
+                // Ensure we're up to date with the existing consent
+                getExistingConsent();
+                // Prevent the modal from being closed with anything
+                // but the buttons
+                $('#cookieConsentModal')
+                    .modal({
+                        backdrop: 'static',
+                        keyboard: false,
+                        focus: true,
+                        show: true
+                    });
+            }
+        );
+        $('.consentCheckbox').on('click', function () {
+            maintainSelected();
+        });
+    }
+
+    // On page load, run any code that has been given consent
+    runConsentedCode();
+})();
-- 
2.37.1 (Apple Git-137.1)