Make your Ghost blog and your Caspar theme GDPR compatible

Hey! Below I have listed a few steps how to make your casper theme and your Ghost blog compatible to the new european data privacy act. I assum that you use the latest version of the Caspar theme (2.2.1) and the latest version of Ghost (1.22.7).

2018-06-08 Update: Due to some JavaScript errors I have updated the jQuery section. Do not forget to remove the integrity and the crossorigin attributes from the script tag!
2018-06-08 Update: Added a section for a cookie bar

1. Download jQuery to your theme

Casper uses by default the jquery CDN. But jQuery is not part of the Privacy Shield. So we have to store the library localy in our theme:

  1. Go to your Ghost backend and open the design options
  2. Download the latest Caspar theme from there
  3. Extract the theme
  4. Open package.json and edit the name in the first line if you dont want to override the main theme
  5. Then open default.hbs and replace src="https://code.jquery.com/jquery-3.2.1.min.js" with src="{{asset "js/jquery-3.2.1.min.js"}}" at line 60
  6. Also remove the integrity and the crossorigin attributes from the script tag.
  7. Download https://code.jquery.com/jquery-3.2.1.min.js and save it to yourthemefolder/assetes/js/jquery-3.2.1.min.js
  8. Zip you theme folder again, upload it to your Ghost blog and activate it

Verify that jQuery is now loading from your theme instead from the CDN by using the developer tools of your browser.

2. Disable Gravatar

Automattic, the company behind Gravatar is part of the Privacy Shield. But at the moment I think its far too complex for me to alter my privacy policy, conclude a contract and add the data flow to my public data procedure directory. So I decided to switch it off. If you have a Gravatar Account connected to your mail, Ghost will automatically pull your Gravatar image to the blog frontend. You can avoid that by following the steps below:

  1. Go to your Ghost backend
  2. Open the Team section
  3. Click on every user and upload a custom user image

You should verify that the pictures in the frontend are not longer provided by Gravatar by using the DOM inspector.

3. What about Disqus?

Disqus provides a cool commenting function that is by default disabled in your Caspar theme. But you can easily enable it by following this guide.

If you have enabled Disqus dont worry. Disqus has now joined the Privacy Shield. That means that you can use it in your blog in general. All you have to do is:

  1. Add a Disqus section to your privacy policy
  2. Add Disqus data flow to your public procedure directory
  3. Conclude a contract with Disqus about the data processing

Yes, that sounds complex. But this is the only way you can legal use Disqus. If you do not want all the hassle keep it disabled.

4. Add a cookie bar

This step is only necessary if you are using services like GoogleAnalytics, Matomo or Piwik or if you have custom changes that will store cookies in the clients browser. By default Ghost will not store any cookies except you are logged into the backend. Note: I would prefer storing the cookie bar script inside the Ghost backend rather than alter the theme because theme changes are always harder to maintain over time.

  1. Go to https://www.primebox.co.uk/projects/jquery-cookiebar/ and download the zipped script
  2. Go to your Ghost backend and open the code injection menu
  3. Extract the zip contents and copy the contents of the js and css files into the blog footer section like below. Important: Do not copy this into the head section! Do not forget to append the init script from the index.html file:
<!-- jquery.cookiebar.js -->
<script>
(function($){
	$.cookieBar = function(options,val){
		if(options=='cookies'){
			var doReturn = 'cookies';
		}else if(options=='set'){
			var doReturn = 'set';
		}else{
			var doReturn = false;
		}
		var defaults = {
			message: 'We use cookies to track usage and preferences.', //Message displayed on bar
			acceptButton: true, //Set to true to show accept/enable button
			acceptText: 'I Understand', //Text on accept/enable button
			acceptFunction: function(cookieValue){if(cookieValue!='enabled' && cookieValue!='accepted') window.location = window.location.href;}, //Function to run after accept
			declineButton: false, //Set to true to show decline/disable button
			declineText: 'Disable Cookies', //Text on decline/disable button
			declineFunction: function(cookieValue){if(cookieValue=='enabled' || cookieValue=='accepted') window.location = window.location.href;}, //Function to run after decline
			policyButton: false, //Set to true to show Privacy Policy button
			policyText: 'Privacy Policy', //Text on Privacy Policy button
			policyURL: '/privacy-policy/', //URL of Privacy Policy
			autoEnable: true, //Set to true for cookies to be accepted automatically. Banner still shows
			acceptOnContinue: false, //Set to true to accept cookies when visitor moves to another page
			acceptOnScroll: false, //Set to true to accept cookies when visitor scrolls X pixels up or down
			acceptAnyClick: false, //Set to true to accept cookies when visitor clicks anywhere on the page
			expireDays: 365, //Number of days for cookieBar cookie to be stored for
			renewOnVisit: false, //Renew the cookie upon revisit to website
			forceShow: false, //Force cookieBar to show regardless of user cookie preference
			effect: 'slide', //Options: slide, fade, hide
			element: 'body', //Element to append/prepend cookieBar to. Remember "." for class or "#" for id.
			append: false, //Set to true for cookieBar HTML to be placed at base of website. Actual position may change according to CSS
			fixed: false, //Set to true to add the class "fixed" to the cookie bar. Default CSS should fix the position
			bottom: false, //Force CSS when fixed, so bar appears at bottom of website
			zindex: '', //Can be set in CSS, although some may prefer to set here
			domain: String(window.location.hostname), //Location of privacy policy
			referrer: String(document.referrer) //Where visitor has come from
		};
		var options = $.extend(defaults,options);
		
		//Sets expiration date for cookie
		var expireDate = new Date();
		expireDate.setTime(expireDate.getTime()+(options.expireDays*86400000));
		expireDate = expireDate.toGMTString();
		
		var cookieEntry = 'cb-enabled={value}; expires='+expireDate+'; path=/';
		
		//Retrieves current cookie preference
		var i,cookieValue='',aCookie,aCookies=document.cookie.split('; ');
		for (i=0;i<aCookies.length;i++){
			aCookie = aCookies[i].split('=');
			if(aCookie[0]=='cb-enabled'){
    			cookieValue = aCookie[1];
			}
		}
		//Sets up default cookie preference if not already set
		if(cookieValue=='' && doReturn!='cookies' && options.autoEnable){
			cookieValue = 'enabled';
			document.cookie = cookieEntry.replace('{value}','enabled');
		}else if((cookieValue=='accepted' || cookieValue=='declined') && doReturn!='cookies' && options.renewOnVisit){
			document.cookie = cookieEntry.replace('{value}',cookieValue);
		}
		if(options.acceptOnContinue){
			if(options.referrer.indexOf(options.domain)>=0 && String(window.location.href).indexOf(options.policyURL)==-1 && doReturn!='cookies' && doReturn!='set' && cookieValue!='accepted' && cookieValue!='declined'){
				doReturn = 'set';
				val = 'accepted';
			}
		}
		if(doReturn=='cookies'){
			//Returns true if cookies are enabled, false otherwise
			if(cookieValue=='enabled' || cookieValue=='accepted'){
				return true;
			}else{
				return false;
			}
		}else if(doReturn=='set' && (val=='accepted' || val=='declined')){
			//Sets value of cookie to 'accepted' or 'declined'
			document.cookie = cookieEntry.replace('{value}',val);
			if(val=='accepted'){
				return true;
			}else{
				return false;
			}
		}else{
			//Sets up enable/accept button if required
			var message = options.message.replace('{policy_url}',options.policyURL);
			
			if(options.acceptButton){
				var acceptButton = '<a href="" class="cb-enable">'+options.acceptText+'</a>';
			}else{
				var acceptButton = '';
			}
			//Sets up disable/decline button if required
			if(options.declineButton){
				var declineButton = '<a href="" class="cb-disable">'+options.declineText+'</a>';
			}else{
				var declineButton = '';
			}
			//Sets up privacy policy button if required
			if(options.policyButton){
				var policyButton = '<a href="'+options.policyURL+'" class="cb-policy">'+options.policyText+'</a>';
			}else{
				var policyButton = '';
			}
			//Whether to add "fixed" class to cookie bar
			if(options.fixed){
				if(options.bottom){
					var fixed = ' class="fixed bottom"';
				}else{
					var fixed = ' class="fixed"';
				}
			}else{
				var fixed = '';
			}
			if(options.zindex!=''){
				var zindex = ' style="z-index:'+options.zindex+';"';
			}else{
				var zindex = '';
			}
			
			//Displays the cookie bar if arguments met
			if(options.forceShow || cookieValue=='enabled' || cookieValue==''){
				if(options.append){
					$(options.element).append('<div id="cookie-bar"'+fixed+zindex+'><p>'+message+acceptButton+declineButton+policyButton+'</p></div>');
				}else{
					$(options.element).prepend('<div id="cookie-bar"'+fixed+zindex+'><p>'+message+acceptButton+declineButton+policyButton+'</p></div>');
				}
			}
			
			var removeBar = function(func){
				if(options.acceptOnScroll) $(document).off('scroll');
				if(typeof(func)==='function') func(cookieValue);
				if(options.effect=='slide'){
					$('#cookie-bar').slideUp(300,function(){$('#cookie-bar').remove();});
				}else if(options.effect=='fade'){
					$('#cookie-bar').fadeOut(300,function(){$('#cookie-bar').remove();});
				}else{
					$('#cookie-bar').hide(0,function(){$('#cookie-bar').remove();});
				}
				$(document).unbind('click',anyClick);
			};
			var cookieAccept = function(){
				document.cookie = cookieEntry.replace('{value}','accepted');
				removeBar(options.acceptFunction);
			};
			var cookieDecline = function(){
				var deleteDate = new Date();
				deleteDate.setTime(deleteDate.getTime()-(864000000));
				deleteDate = deleteDate.toGMTString();
				aCookies=document.cookie.split('; ');
				for (i=0;i<aCookies.length;i++){
					aCookie = aCookies[i].split('=');
					if(aCookie[0].indexOf('_')>=0){
						document.cookie = aCookie[0]+'=0; expires='+deleteDate+'; domain='+options.domain.replace('www','')+'; path=/';
					}else{
						document.cookie = aCookie[0]+'=0; expires='+deleteDate+'; path=/';
					}
				}
				document.cookie = cookieEntry.replace('{value}','declined');
				removeBar(options.declineFunction);
			};
			var anyClick = function(e){
				if(!$(e.target).hasClass('cb-policy')) cookieAccept();
			};
			
			$('#cookie-bar .cb-enable').click(function(){cookieAccept();return false;});
			$('#cookie-bar .cb-disable').click(function(){cookieDecline();return false;});
			if(options.acceptOnScroll){
				var scrollStart = $(document).scrollTop(),scrollNew,scrollDiff;
				$(document).on('scroll',function(){
					scrollNew = $(document).scrollTop();
					if(scrollNew>scrollStart){
						scrollDiff = scrollNew - scrollStart;
					}else{
						scrollDiff = scrollStart - scrollNew;
					}
					if(scrollDiff>=Math.round(options.acceptOnScroll)) cookieAccept();
				});
			}
			if(options.acceptAnyClick){
				$(document).bind('click',anyClick);
			}
		}
	};
})(jQuery);
<!-- This initiates the cookie bar -->
$(document).ready(function(){
    $.cookieBar({
        message: 'This website uses cookies to track usage and preferences',
        acceptText: 'I Understand',
        policyButton: true,
        policyText: 'Privacy Policy',
        policyURL: '/privacy-policy/',
        bottom: true,
        fixed: true,
        zindex: '999',
    });
});
</script>
<style>
#cookie-bar {background:#090a0b; height:auto; line-height:24px; color:#fff; text-align:center; padding:3px 0;}
#cookie-bar.fixed {position:fixed; top:0; left:0; width:100%;}
#cookie-bar.fixed.bottom {bottom:0; top:auto;}
#cookie-bar p {margin:0; padding:0;}
#cookie-bar a {color:#ffffff; display:inline-block; border-radius:3px; text-decoration:none; padding:0 6px; margin-left:8px;}
#cookie-bar .cb-enable {background:#26a8ed;}
#cookie-bar .cb-enable:hover {background:#26a8ed;}
#cookie-bar .cb-disable {background:#26a8ed;}
#cookie-bar .cb-disable:hover {background:#26a8ed;}
#cookie-bar .cb-policy {background:#26a8ed;}
#cookie-bar .cb-policy:hover {background:#26a8ed;}
</style>
<!-- END jquery.cookiebar.js -->
  1. Edit the style and initiation settings. (In this example I have already added the Caspar default colors) If you need a full option list visit the documentation at https://www.primebox.co.uk/projects/jquery-cookiebar/
  2. Do not forget to set the correct url to your privacy policy site in the initiation options
  3. Add detailed cookie infos to your privacy policy. Do not forget to add the cb-enabled cookie which is used by the cookie bar to track the consent.
Make your Ghost blog and your Caspar theme GDPR compatible
Share this