cookie_content_blocker.module in Cookie Content Blocker 7
Same filename and directory in other branches
Contains the main module code for Cookie content blocker.
File
cookie_content_blocker.moduleView source
<?php
/**
* @file
* Contains the main module code for Cookie content blocker.
*/
/**
* Implements hook_help().
*/
function cookie_content_blocker_help($path, array $arg) {
if ($path !== 'admin/help#cookie_content_blocker') {
return '';
}
$output = file_get_contents(drupal_get_path('module', 'cookie_content_blocker') . '/README.md');
return module_exists('markdown') ? filter_xss_admin(module_invoke('markdown', 'filter', 'process', 0, -1, $output)) : '<pre>' . check_plain($output) . '</pre>';
}
/**
* Implements hook_menu().
*/
function cookie_content_blocker_menu() {
$items = array();
$items['admin/config/system/cookie_content_blocker'] = array(
'title' => 'Cookie content blocker',
'description' => 'Manage all settings related to Cookie content blocker.',
'access arguments' => array(
'administer cookie content blocker',
),
'page callback' => 'drupal_get_form',
'page arguments' => array(
'cookie_content_blocker_settings_form',
),
'file' => 'cookie_content_blocker.admin.inc',
);
return $items;
}
/**
* Implements hook_permission().
*/
function cookie_content_blocker_permission() {
return array(
'administer cookie content blocker' => array(
'title' => t('Administer Cookie content blocker'),
'description' => t("Allow users to configure Cookie content blocker's settings."),
),
);
}
/**
* Implements hook_theme().
*/
function cookie_content_blocker_theme($existing, $type, $theme, $path) {
return array(
'cookie_content_blocker_wrapper' => array(
'render element' => 'element',
'template' => 'theme/cookie-content-blocker-wrapper',
),
);
}
/**
* Implements hook_filter_info().
*/
function cookie_content_blocker_filter_info() {
return array(
'cookie_content_blocker_filter' => array(
'title' => t('Cookie content blocker filter'),
'description' => t("This filter converts Cookie content blocker's custom HTML tags into markup and handles blocking of wrapped HTML/Text. \n It is recommended to run this filter last."),
'process callback' => '_cookie_content_blocker_filter_process',
),
);
}
/**
* Implements hook_page_alter().
*/
function cookie_content_blocker_page_alter(&$page) {
$consent_awareness_settings = variable_get('cookie_content_blocker_consent_awareness', _cookie_consent_blocker_consent_awareness_defaults());
if (empty($consent_awareness_settings)) {
return;
}
drupal_add_library('system', 'jquery.cookie');
drupal_add_js(array(
'cookieContentBlocker' => array(
'consentAwareness' => $consent_awareness_settings,
),
), 'setting');
}
/**
* Implements hook_element_info_alter().
*
* Adds our pre render callback to elements (render arrays with a '#type') in
* order to provide our own defaults that don't overwrite hook_element_info()
* defaults of given element types.
*
* Add a '#cookie_content_blocker' property with a non empty value to your
* element to prevent loading of that elements content until consent has been
* given.
*
* @code
* array(
* '#cookie_content_blocker' => TRUE,
* );
*
* // You can also use an array value with additional settings. Like a
* // message to show the visitor when content is blocked.
*
* array(
* '#cookie_content_blocker' => array(
* 'blocked_message' => t('Your custom message'),
* ),
* );
* @endcode
*
* @see cookie_content_blocker_element_pre_render()
*/
function cookie_content_blocker_element_info_alter(array &$type) {
foreach ($type as $element_type => $element_info) {
if (!isset($element_info['#pre_render'])) {
$element_info['#pre_render'] = array();
}
$element_info['#pre_render'][] = 'cookie_content_blocker_element_pre_render';
$type[$element_type] = $element_info;
}
}
/**
* Pre render elements to provide our theme wrapper and defaults.
*
* @param array $element
* The element being rendered.
*
* @return array
* The pre rendered element.
*
* @see cookie_content_blocker_element_info_alter()
*/
function cookie_content_blocker_element_pre_render(array $element) {
if (empty($element['#cookie_content_blocker'])) {
return $element;
}
if (!is_array($element['#cookie_content_blocker'])) {
$element['#cookie_content_blocker'] = array();
}
$element['#cookie_content_blocker'] += _cookie_content_blocker_element_defaults();
if (empty($element['#theme_wrappers'])) {
$element['#theme_wrappers'] = array();
}
$element = _cookie_content_blocker_element_process_attached($element);
$element['#theme_wrappers'][] = 'cookie_content_blocker_wrapper';
return $element;
}
/**
* Implements hook_preprocess_HOOK() for cookie_content_blocker_wrapper().
*/
function cookie_content_blocker_preprocess_cookie_content_blocker_wrapper(array &$variables) {
$element = $variables['element'];
$options = $element['#cookie_content_blocker'];
$variables['preview'] = is_array($options['preview']) ? drupal_render($options['preview']) : $options['preview'];
$blocked_message_raw = is_array($options['blocked_message']) ? drupal_render($options['blocked_message']) : $options['blocked_message'];
$variables['blocked_message'] = filter_xss($blocked_message_raw);
$variables['original_content'] = _cookie_content_blocker_replace_scripts_with_fake($element['#children']);
$variables['classes_array'] = array_merge($variables['classes_array'], array(
'js-cookie-content-blocker',
'cookie-content-blocker',
));
$variables['show_button'] = $options['show_button'];
$variables['show_placeholder'] = $options['show_placeholder'];
$variables['button_text'] = check_plain($options['button_text']);
$variables['button_classes_array'] = array(
'button',
'cookie-content-blocker__button',
'js-cookie-content-blocker-consent-change-button',
);
if (!$variables['show_button'] && !empty($options['enable_click'])) {
$variables['classes_array'] = array_merge($variables['classes_array'], array(
'js-cookie-content-blocker-click-consent-change',
'cookie-content-blocker--click-consent-change',
));
}
}
/**
* Implements hook_process_HOOK() for cookie_content_blocker_wrapper().
*/
function cookie_content_blocker_process_cookie_content_blocker_wrapper(&$variables) {
$variables['button_classes'] = implode(' ', $variables['button_classes_array']);
}
/**
* Defines defaults used by the Cookie content blocker wrapper.
*
* @return array
* Defaults used for the Cookie content blocker wrapper.
*/
function _cookie_content_blocker_element_defaults() {
$button_text = variable_get('cookie_content_blocker_button_text');
return array(
'blocked_message' => _cookie_content_blocker_blocked_message(),
'show_button' => variable_get('cookie_content_blocker_show_button', TRUE),
'button_text' => empty($button_text) ? t('Show content') : $button_text,
'enable_click' => variable_get('cookie_content_blocker_enable_click_consent_change', TRUE),
'show_placeholder' => TRUE,
'preview' => array(),
);
}
/**
* The default message for disabled elements while cookies are not accepted.
*
* @return string
* The default message.
*/
function _cookie_content_blocker_blocked_message() {
$default_message = variable_get('cookie_content_blocker_blocked_message');
return empty($default_message) ? t('You have not yet given permission to place the required cookies. Accept the required cookies to view this content.') : $default_message;
}
/**
* Process attached scripts, styles and libraries.
*
* @todo maybe support blocking other attached things than JS, like libs and
* css.
*
* @param array $element
* The element.
*
* @return array
* The processed element.
*/
function _cookie_content_blocker_element_process_attached(array $element) {
if (empty($element['#attached'])) {
return $element;
}
$element = _cookie_content_blocker_element_process_attached_js($element);
return $element;
}
/**
* Processes element's attached JavaScript.
*
* Attached scripts to this element should not be loaded before cookies are
* accepted. Therefor placeholders are placed instead of the actual scripts.
* The placeholders will be replaced with the original scripts when cookies are
* accepted.
*
* @param array $element
* The render element.
*
* @return array
* The processed element.
*/
function _cookie_content_blocker_element_process_attached_js(array $element) {
if (empty($element['#attached']['js'])) {
return $element;
}
$original_js = drupal_add_js();
$js =& drupal_static('drupal_add_js');
// Overwrite the statically cached JavaScript in order to keep the settings
// and default added Drupal JS. This way we maintain the structure of
// JavaScript settings when we call drupal_process_attached() and don't have
// to worry about managing attached settings to our element separately.
$default_js = array(
'settings' => 'settings',
'misc/drupal.js' => 'misc/drupal.js',
);
$js = array_intersect_key($original_js, $default_js);
drupal_process_attached(array(
'#attached' => array(
'js' => $element['#attached']['js'],
),
));
// Once processing is done we must make sure any additional settings added
// are merged into the actual JavaScript array.
$attached_js = drupal_add_js();
$js = array_merge($original_js, array_intersect_key($attached_js, $default_js));
// All element's attached JavaScript that is not settings or the default
// drupal.js will remain to be loaded later via a placeholder and when consent
// is given.
$attached_js = array_diff_key($attached_js, $default_js);
foreach ($attached_js as $key => $item) {
$item['preprocess'] = $item['cache'] = FALSE;
$original_script = drupal_get_js($item['scope'], array(
$key => $item,
));
$script_placeholder = _cookie_content_blocker_generate_script_placeholder($original_script);
// We adjust the weight because drupal_add_js() tweaks the weight of added
// items, and we want to maintain the actual weight of the original item.
$weight = $item['weight'] -= count($js) / 1000;
drupal_add_js($script_placeholder, array(
'type' => 'inline',
'scope' => $item['scope'],
'group' => $item['group'],
'weight' => $weight,
));
}
unset($element['#attached']['js']);
return $element;
}
/**
* Generate an inline placeholder script referencing to the original script.
*
* @param string $original_script
* The original script HTML.
*
* @return string
* The generated inline script placeholder.
*/
function _cookie_content_blocker_generate_script_placeholder($original_script) {
$id = drupal_html_id(drupal_random_key());
drupal_add_js(array(
'cookieContentBlocker' => array(
'scripts' => array(
$id => $original_script,
),
),
), 'setting');
// We insert a placeholder script that sets its own data attribute when the
// document is loaded. We need that data attribute as a reference to the
// original script stored in settings, so we can replace it when cookies are
// accepted. Currently drupal_add_js() does not support custom script
// attributes, and therefor this is the only way to achieve this.
// See https://www.drupal.org/project/drupal/issues/1664602
return sprintf('jQuery("script").last().attr("data-cookie-content-blocker-script-id", "%s").text("");', $id);
}
/**
* Replace scripts in HTML with a placeholder fake script tag.
*
* We need a fake script tag to not break the outer <script> tag with type
* 'text/plain'. It essentially provides us with the posibility to store the
* original content inline without the browser evaluating it.
*
* @param string $html
* The original HTML.
*
* @return string
* The HTML with replaced scripts.
*
* @see cookie-content-blocker-wrapper.tpl.php
*/
function _cookie_content_blocker_replace_scripts_with_fake($html) {
return preg_replace('/(<[\\/]?script)/', "\$1fake", $html);
}
/**
* Process callback for the Cookie content blocker text filter.
*
* @param string $text
* The text being processed.
* @param object $filter
* The filter.
* @param object $format
* The format the text is being processed in.
* @param string $langcode
* The language code.
* @param bool $cache
* Whether the filtered text can be cached.
* @param string $cache_id
* The cache ID.
*
* @return string
* The filtered text.
*/
function _cookie_content_blocker_filter_process($text, $filter, $format, $langcode, $cache, $cache_id) {
if (empty($text)) {
return $text;
}
return _cookie_content_blocker_replace_tags($text);
}
/**
* Replaces cookie content blocker tags for the given text.
*
* @param string $text
* The HTML/Text where we want to replace tags for.
*
* @return string
* The original string with tags replaced.
*/
function _cookie_content_blocker_replace_tags($text) {
list($matches, $settings, $content) = _cookie_content_blocker_match_tags($text);
if (empty($matches)) {
return $text;
}
foreach ($matches as $index => $match) {
if (empty($content[$index])) {
continue;
}
$blocker_settings = isset($settings[$index]) ? _cookie_content_blocker_settings_decode($settings[$index]) : array();
$blocked_content_element = array(
// We depend on other filters to have sanitized the content.
'#markup' => $content[$index],
'#cookie_content_blocker' => !empty($blocker_settings) ? $blocker_settings : TRUE,
);
$blocked_content = drupal_render($blocked_content_element);
$text = str_replace($match, $blocked_content, $text);
}
return $text;
}
/**
* Decode a encoded settings string to a settings array.
*
* @param string $settings
* The settings string to decode.
*
* @return array
* The decoded settings or an empty array if decoding failed.
*/
function _cookie_content_blocker_settings_decode($settings) {
// Check if we need to decode the settings first. This way we support
// both base64 encoded strings and settings already in a JSON-string format.
$decoded_settings = base64_decode($settings);
$needs_decode = base64_encode($decoded_settings) === $settings;
if ($needs_decode) {
$settings = $decoded_settings;
}
$settings = filter_xss($settings, array());
$settings = drupal_json_decode($settings);
return !empty($settings) ? $settings : array();
}
/**
* Match all our custom HTML element nodes with their settings and children.
*
* We use our own custom non-existent HTML element node '<cookiecontentblocker>'
* using a node element makes it easier to work with in WYSIWYG editors.
*
* @param string $text
* The HTML/Text string.
*
* @return array
* A keyed array containing:
* - (0) An array of full pattern matches.
* - (1) An array of strings defining settings for each associated match.
* - (2) An array of strings defining the content for each associated match.
*/
function _cookie_content_blocker_match_tags($text) {
preg_match_all('/<cookiecontentblocker.*?(?:data-settings="(.*?)".*?)*?>(.*?)<\\/cookiecontentblocker>/s', $text, $matches);
return array(
$matches[0],
$matches[1],
$matches[2],
);
}
/**
* Create a form based on variable information defined by a module.
*
* @param array $form
* The structure of the form.
* @param string $module
* The module to look for a hook_variable_info() definition.
*
* @return array
* The variable form, keyed by variable name.
*/
function _cookie_content_blocker_variable_form(array $form, $module) {
module_load_include('variable.inc', $module);
$variable_info = module_invoke($module, 'variable_info', array());
if (empty($variable_info)) {
return $form;
}
$type_map = array(
'text' => 'textarea',
'boolean' => 'checkbox',
'string' => 'textfield',
);
foreach ($variable_info as $name => $info) {
$current_value = variable_get($name, NULL);
$element = isset($info['element']) ? $info['element'] : array();
$element += array(
'#type' => isset($type_map[$info['type']]) ? $type_map[$info['type']] : $info['type'],
'#title' => $info['title'],
'#description' => $info['description'],
'#default_value' => $current_value === NULL ? $info['default'] : $current_value,
);
if (isset($info['options'])) {
$element += array(
'#options' => $info['options'],
);
}
$form[$name] = $element;
}
return $form;
}
/**
* Get the consent awareness default settings.
*
* @return array
* The default values.
*/
function _cookie_consent_blocker_consent_awareness_defaults() {
$type_default_settings = array(
'cookie' => array(
'operator' => '',
'name' => '',
'value' => '',
),
'event' => array(
'name' => '',
'selector' => '',
),
);
return array(
'accepted' => $type_default_settings,
'declined' => $type_default_settings,
'change' => array(
'event' => array(
'name' => '',
'selector' => '',
),
),
);
}