5 * The main file for the EU Cookie Compliance module.
7 * This module intends to deal with the EU Directive on Privacy and Electronic
8 * Communications that comes into effect in the UK on 26th May 2012.
11 use Drupal\Core\Form\FormStateInterface;
12 use Drupal\Core\Cache\Cache;
14 use Drupal\Component\Utility\UrlHelper;
15 use Drupal\Component\Utility\Html;
16 use Drupal\Core\Routing\RouteMatchInterface;
17 use Drupal\smart_ip\SmartIp;
18 use Drupal\Core\Database\Database;
21 * Implements hook_help().
23 function eu_cookie_compliance_help($route_name, RouteMatchInterface $route_match) {
24 switch ($route_name) {
25 case 'help.page.eu_cookie_compliance':
27 $output .= '<h3>' . t('About') . '</h3>';
28 $output .= '<p>' . t('This module intends to deal with the EU Directive on Privacy and Electronic Communications that comes into effect on 26th May 2012.
29 From that date, if you are not compliant or visibly working towards compliance,
30 you run the risk of enforcement action, which can include a fine of up to
31 half a million pounds for a serious breach.') . '</p>';
32 $output .= '<h3>' . t('How it works') . '</h3>';
33 $output .= '<p>' . t('The module displays a banner at the bottom or the top of website to make users aware of the fact that cookies are being set. The user may then give
34 his/her consent or move to a page that provides more details. Consent is given
35 by user pressing the agree buttons or by continuing browsing the website. Once
36 consent is given another banner appears with a “Thank you” message.') . '</p>';
37 $output .= '<p>' . t('The module provides a settings page where the banner can be customized. There are also template files for the banners that can be overridden by your theme.') . '</p>';
38 $output .= '<h3>' . t('Installation') . '</h3>';
39 $output .= '<ol><p><li>' . t('Unzip the files to the "sites/all/modules" OR "modules" directory and enable the module.') . '</li></p>';
40 $output .= '<p><li>' . t('If desired, give the administer EU Cookie Compliance banner permissions that allow users of certain roles access the administration page. You can do so on the admin/user/permissions page.') . '</li></p>';
41 // @codingStandardsIgnoreLine
42 $output .= "<p><label>- </label>" . t("there is also a 'display eu cookie compliance banner' permission that helps you show the banner to the roles you desire.") . "</p>";
43 $output .= '<p><li>' . t('You may want to create a page that would explain how your site uses cookies. Alternatively, if you have a privacy policy, you can link the banner to that page (see next step).') . '</li></p>';
44 $output .= '<p><li>' . t('Go to the admin/config/system/eu-cookie-compliance page to configure and enable the banner.') . '</li></p>';
45 $output .= '<p><li>' . t('If you want to customize the banner background and text color, either type in the hex values or simply install http://drupal.org/project/jquery_colorpicker.') . '</li></p>';
46 $output .= '<p><li>' . t('If you want to theme your banner override the themes in the template file.') . '</li></p>';
47 $output .= '<p><li>' . t('If you want to show the message in EU countries only, install the Smart IP module: http://drupal.org/project/smart_ip and enable the option on the admin page.') . '</li></p></ol>';
48 $output .= '<p><b>' . t('NOTICE: The module does not audit your cookies nor does it prevent cookies from being set.') . '</b></p>';
49 $output .= '<h3>' . t('For developers') . '</h3>';
50 $output .= '<p>' . t('If you want to conditionally set cookies in your module, there is a javascript function provided that returns TRUE if the current user has given his consent:') . '</p>';
51 $output .= '<p><code>Drupal.eu_cookie_compliance.hasAgreed()</code></p>';
53 return ['#markup' => $output];
58 * Implements hook_page_attachments().
60 function eu_cookie_compliance_page_attachments(&$attachments) {
61 $config = Drupal::config('eu_cookie_compliance.settings');
63 // Check Add/Remove domains.
65 $domain_option = $config->get('domains_option');
67 if (!empty($config->get('domains_list'))) {
70 $domains_list = str_replace(array("\r\n", "\r"), "\n", $config->get('domains_list'));
71 $domains_list = explode("\n", $domains_list);
72 $domains_list = preg_replace('{/$}', '', $domains_list);
73 $domain_match = in_array($base_url, $domains_list);
75 if ($domain_option && $domain_match) {
76 $domain_allow = FALSE;
79 if (!$domain_option && !$domain_match) {
80 $domain_allow = FALSE;
84 // Check exclude paths.
87 if (!empty($config->get('exclude_paths'))) {
88 $path = Drupal::service('path.current')->getPath();
89 $path_match = Drupal::service('path.matcher')->matchPath($path, $config->get('exclude_paths'));
90 $exclude_paths = $config->get('exclude_paths');
91 Drupal::moduleHandler()->alter('eu_cookie_compliance_path_match', $path_match, $path, $exclude_paths);
94 // Check hide cookie compliance on admin theme.
95 $admin_theme_match = FALSE;
97 if ($config->get('exclude_admin_theme')) {
98 // Determines whether the active route is an admin one.
99 $is_route_admin = Drupal::service('router.admin_context')->isAdminRoute();
100 if ($is_route_admin) {
101 $admin_theme_match = TRUE;
106 if (!empty($config->get('eu_only')) && $config->get('eu_only')) {
107 $geoip_match = eu_cookie_compliance_user_in_eu();
110 // Allow other modules to alter the geo IP matching logic.
111 Drupal::moduleHandler()->alter('eu_cookie_compliance_geoip_match', $geoip_match);
114 if (Drupal::currentUser()->id() == 1 && !empty($config->get('exclude_uid_1')) && $config->get('exclude_uid_1')) {
118 // Allow other modules to alter if the banner needs to be shown or not.
119 $modules_allow_popup = TRUE;
120 Drupal::moduleHandler()->alter('eu_cookie_compliance_show_popup', $modules_allow_popup);
122 if ($config->get('popup_enabled') && Drupal::currentUser()->hasPermission('display eu cookie compliance popup') && $geoip_match && $domain_allow && !$path_match && !$admin_theme_match && $uid1_match && $modules_allow_popup) {
123 $language = Drupal::languageManager()->getCurrentLanguage();
127 if ($config->get('popup_bg_hex') !== '' && $config->get('popup_text_hex') !== '') {
128 $data['css'] = 'div#sliding-popup {background:#' . Html::escape($config->get('popup_bg_hex')) . '} #sliding-popup h1, #sliding-popup h2, #sliding-popup h3, #sliding-popup p { color:#' . Html::escape($config->get('popup_text_hex')) . ';}';
130 if (!empty($config->get('popup_position')) && $config->get('popup_position') && !empty($config->get('fixed_top_position')) && $config->get('fixed_top_position')) {
131 $data['css'] .= '#sliding-popup.sliding-popup-top { position: fixed; }';
134 $popup_text_info = str_replace(array("\r", "\n"), '', $config->get('popup_info.value'));
135 $popup_text_agreed = str_replace(array("\r", "\n"), '', $config->get('popup_agreed.value'));
137 '#theme' => 'eu_cookie_compliance_popup_info',
138 '#message' => check_markup($popup_text_info, $config->get('popup_info.format'), FALSE),
139 '#agree_button' => $config->get('popup_agree_button_message'),
140 '#disagree_button' => ($config->get('show_disagree_button') == TRUE) ? $config->get('popup_disagree_button_message') : FALSE,
142 $mobile_popup_text_info = str_replace(array("\r", "\n"), '', $config->get('mobile_popup_info.value'));
143 $mobile_html_info = array(
144 '#theme' => 'eu_cookie_compliance_popup_info',
145 '#message' => check_markup($mobile_popup_text_info, $config->get('popup_info.format'), FALSE),
146 '#agree_button' => $config->get('popup_agree_button_message'),
147 '#disagree_button' => ($config->get('show_disagree_button') == TRUE) ? $config->get('popup_disagree_button_message') : FALSE,
149 $html_agreed = array(
150 '#theme' => 'eu_cookie_compliance_popup_agreed',
151 '#message' => check_markup($popup_text_agreed, $config->get('popup_agreed.format'), FALSE),
152 '#hide_button' => $config->get('popup_hide_button_message'),
153 '#find_more_button' => ($config->get('show_disagree_button') == TRUE) ? $config->get('popup_find_more_button_message') : FALSE,
156 $was_debugging = FALSE;
159 * @var $twig_service Twig_Environment
161 $twig_service = Drupal::service('twig');
163 if ($twig_service->isDebug()) {
164 $was_debugging = TRUE;
165 $twig_service->disableDebug();
168 $html_info = trim(Drupal::service('renderer')->renderRoot($html_info)->__toString());
169 $mobile_html_info = trim(Drupal::service('renderer')->renderRoot($mobile_html_info)->__toString());
170 $html_agreed = trim(Drupal::service('renderer')->renderRoot($html_agreed)->__toString());
172 if ($was_debugging) {
173 $twig_service->enableDebug();
176 $popup_link = $config->get('popup_link');
177 if (UrlHelper::isExternal($popup_link)) {
178 $popup_link = Url::fromUri($popup_link);
181 $popup_link = $popup_link === '<front>' ? '/' : $popup_link;
182 $popup_link = Url::fromUserInput($popup_link);
184 $popup_link = $popup_link->toString();
186 $data['variables'] = array(
187 'popup_enabled' => $config->get('popup_enabled'),
188 'popup_agreed_enabled' => $config->get('popup_agreed_enabled'),
189 'popup_hide_agreed' => $config->get('popup_hide_agreed'),
190 'popup_clicking_confirmation' => $config->get('popup_clicking_confirmation'),
191 'popup_scrolling_confirmation' => $config->get('popup_scrolling_confirmation'),
192 'popup_html_info' => $config->get('popup_enabled') ? $html_info : FALSE,
193 'use_mobile_message' => !empty($config->get('use_mobile_message')) ? $config->get('use_mobile_message') : FALSE,
194 'mobile_popup_html_info' => $config->get('popup_enabled') ? $mobile_html_info : FALSE,
195 'mobile_breakpoint' => !empty($config->get('mobile_breakpoint')) ? $config->get('mobile_breakpoint') : '768',
196 'popup_html_agreed' => $config->get('popup_agreed_enabled') ? $html_agreed : FALSE,
197 'popup_use_bare_css' => !empty($config->get('popup_use_bare_css')) ? $config->get('popup_use_bare_css') : FALSE,
198 'popup_height' => !empty($config->get('popup_height')) ? $config->get('popup_height') : 'auto',
199 'popup_width' => !empty($config->get('popup_width')) ? $config->get('popup_width') : '100%',
200 'popup_delay' => (int) ($config->get('popup_delay')),
201 'popup_link' => $popup_link,
202 'popup_link_new_window' => $config->get('popup_link_new_window'),
203 'popup_position' => $config->get('popup_position'),
204 'popup_language' => $language->getId(),
205 'better_support_for_screen_readers' => !empty($config->get('better_support_for_screen_readers')) ? $config->get('better_support_for_screen_readers') : FALSE,
206 'cookie_name' => !empty($config->get('cookie_name')) ? $config->get('cookie_name') : '',
207 'reload_page' => !empty($config->get('reload_page')) ? $config->get('reload_page') : FALSE,
208 'domain' => $config->get('domain'),
209 'popup_eu_only_js' => !empty($config->get('eu_only_js')) ? $config->get('eu_only_js') : FALSE,
210 'cookie_lifetime' => $config->get('cookie_lifetime'),
211 'disagree_do_not_show_popup' => !empty($config->get('disagree_do_not_show_popup')) ? $config->get('disagree_do_not_show_popup') : FALSE,
214 $attachments['#attached']['drupalSettings']['eu_cookie_compliance'] = $data['variables'];
215 if ($config->get('use_bare_css')) {
216 $attachments['#attached']['library'][] = 'eu_cookie_compliance/eu_cookie_compliance_bare';
219 $attachments['#attached']['library'][] = 'eu_cookie_compliance/eu_cookie_compliance';
222 $attachments['#attached']['html_head'][] = [
225 '#value' => $data['css'],
227 'eu-cookie-compliance-css',
229 $cache_tags = isset($attachments['#cache']['tags']) ? $attachments['#cache']['tags'] : [];
230 $attachments['#cache']['tags'] = Cache::mergeTags($cache_tags, $config->getCacheTags());
235 * Implements hook_theme().
237 function eu_cookie_compliance_theme($existing, $type, $theme, $path) {
239 'eu_cookie_compliance_popup_info' => array(
240 'template' => 'eu_cookie_compliance_popup_info',
241 'variables' => array(
243 'agree_button' => NULL,
244 'disagree_button' => NULL,
247 'eu_cookie_compliance_popup_agreed' => array(
248 'template' => 'eu_cookie_compliance_popup_agreed',
249 'variables' => array(
251 'hide_button' => NULL,
252 'find_more_button' => NULL,
259 * Validate field for a HEX value if a value is set.
261 * @param array $element
263 * @param \Drupal\Core\Form\FormStateInterface $form_state
264 * Form State Interface.
266 function eu_cookie_compliance_validate_hex($element, FormStateInterface &$form_state) {
267 if (!empty($element['#value']) && !preg_match('/^[0-9a-fA-F]{3,6}$/', $element['#value'])) {
268 $form_state->setError($element, t('%name must be a HEX value (without leading #) or empty.', array('%name' => $element['#title'])));
273 * Check if the user is in the EU.
275 function eu_cookie_compliance_user_in_eu() {
276 $geoip_match = FALSE;
277 $eu_countries_default = array(
278 NULL, 'BE', 'BG', 'CZ', 'DK', 'DE', 'EE', 'IE', 'EL', 'ES', 'FR', 'HR',
279 'IT', 'CY', 'LV', 'LT', 'LU', 'HU', 'MT', 'NL', 'AT', 'PL', 'PT', 'RO',
280 'SI', 'SK', 'FI', 'SE', 'UK', 'GB', 'NO',
282 // Allow custom array of countries to be loaded from settings.php, defaulting
283 // to the array above.
284 $config = Drupal::config('eu_cookie_compliance.settings');
285 $eu_countries = !empty($config->get('eu_countries')) ? $config->get('eu_countries') : $eu_countries_default;
287 $ip_address = Drupal::request()->getClientIp();
289 $country_code = extension_loaded('geoip') ? geoip_country_code_by_name($ip_address) : '';
290 if (Drupal::moduleHandler()->moduleExists('smart_ip')) {
291 $smart_ip_session = SmartIp::query($ip_address);
292 $country_code = isset($smart_ip_session['countryCode']) ? $smart_ip_session['countryCode'] : NULL;
294 if (in_array($country_code, $eu_countries) || $country_code == '' || $country_code == '-') {
299 'country' => $country_code,
300 'in_eu' => $geoip_match,
305 * Attempt to find the cookie/privacy policy by searching for common titles.
307 * @return bool|string
308 * URL to the node if found, otherwise FALSE.
310 function _eu_cookie_compliance_find_privacy_policy() {
311 $pattern = 'privacy|privacy +policy|cookie +policy|terms +of +use|terms +of +service|terms +and +conditions';
313 $connection = Database::getConnection();
314 // Select operator based on the database type.
315 switch ($connection->databaseType()) {
328 $query = \Drupal::entityQuery('node')
329 ->condition('title', $pattern, $op);
331 $result = $query->execute();
332 if (!empty($result)) {
333 return ('/node/' . array_shift($result));