You are here

cookie_content_blocker.module in Cookie Content Blocker 7

Same filename and directory in other branches
  1. 8 cookie_content_blocker.module

Contains the main module code for Cookie content blocker.

File

cookie_content_blocker.module
View 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' => '',
      ),
    ),
  );
}

Functions

Namesort descending Description
cookie_content_blocker_element_info_alter Implements hook_element_info_alter().
cookie_content_blocker_element_pre_render Pre render elements to provide our theme wrapper and defaults.
cookie_content_blocker_filter_info Implements hook_filter_info().
cookie_content_blocker_help Implements hook_help().
cookie_content_blocker_menu Implements hook_menu().
cookie_content_blocker_page_alter Implements hook_page_alter().
cookie_content_blocker_permission Implements hook_permission().
cookie_content_blocker_preprocess_cookie_content_blocker_wrapper Implements hook_preprocess_HOOK() for cookie_content_blocker_wrapper().
cookie_content_blocker_process_cookie_content_blocker_wrapper Implements hook_process_HOOK() for cookie_content_blocker_wrapper().
cookie_content_blocker_theme Implements hook_theme().
_cookie_consent_blocker_consent_awareness_defaults Get the consent awareness default settings.
_cookie_content_blocker_blocked_message The default message for disabled elements while cookies are not accepted.
_cookie_content_blocker_element_defaults Defines defaults used by the Cookie content blocker wrapper.
_cookie_content_blocker_element_process_attached Process attached scripts, styles and libraries.
_cookie_content_blocker_element_process_attached_js Processes element's attached JavaScript.
_cookie_content_blocker_filter_process Process callback for the Cookie content blocker text filter.
_cookie_content_blocker_generate_script_placeholder Generate an inline placeholder script referencing to the original script.
_cookie_content_blocker_match_tags Match all our custom HTML element nodes with their settings and children.
_cookie_content_blocker_replace_scripts_with_fake Replace scripts in HTML with a placeholder fake script tag.
_cookie_content_blocker_replace_tags Replaces cookie content blocker tags for the given text.
_cookie_content_blocker_settings_decode Decode a encoded settings string to a settings array.
_cookie_content_blocker_variable_form Create a form based on variable information defined by a module.