You are here

gdpr.module in General Data Protection Regulation 7

Same filename and directory in other branches
  1. 8.2 gdpr.module
  2. 8 gdpr.module
  3. 3.0.x gdpr.module

Contains hook implementations and shared functions.

File

gdpr.module
View source
<?php

/**
 * @file
 * Contains hook implementations and shared functions.
 */

/**
 * Implements hook_help().
 */
function gdpr_help($path, $arg) {
  switch ($path) {
    case 'admin/help#gdpr':
      return t('Provides help for making your drupal site GDPR-compliant.');
  }
}

/**
 * Implements hook_menu().
 */
function gdpr_menu() {

  // Administration pages.
  $items['admin/config/gdpr'] = [
    'title' => 'GDPR',
    'position' => 'left',
    'page callback' => 'system_admin_menu_block_page',
    'access arguments' => [
      'administer site configuration',
    ],
    'file' => 'system.admin.inc',
    'file path' => drupal_get_path('module', 'system'),
  ];
  $items['user/%user/collected_data'] = [
    'title' => 'All your data',
    'page callback' => 'gdpr_collected_user_data',
    'page arguments' => [
      1,
    ],
    'access callback' => 'gdpr_collected_user_data_access',
    'access arguments' => [
      1,
    ],
    'type' => MENU_LOCAL_TASK,
  ];
  return $items;
}

/**
 * Implements hook_theme().
 */
function gdpr_theme() {
  return [
    'user_data_page' => [
      'template' => 'gdpr-user-data-page',
      'path' => drupal_get_path('module', 'gdpr') . '/templates',
    ],
  ];
}

/**
 * Implements hook_checklistapi_checklist_info().
 */
function gdpr_checklistapi_checklist_info() {
  $definitions = [];
  $content = gdpr_search_content();
  if (!empty($content)) {
    $description = [
      '#theme' => 'item_list',
    ];
    foreach ($content as $node) {
      if (module_exists('i18n_node')) {
        global $language;
        $tr = translation_node_get_translations($node->tnid);
        if (isset($tr[$language->language])) {
          $node = $tr[$language->language];
        }
      }
      if ($node->status) {
        $status = [
          '#prefix' => '<span class="admin-enabled">',
          '#suffix' => '</span>',
          '#markup' => t('published'),
        ];
      }
      else {
        $status = [
          '#prefix' => '<span class="admin-missing">',
          '#suffix' => '</span>',
          '#markup' => t('not published'),
        ];
      }
      $options = [
        'absolute' => TRUE,
        'attributes' => [
          'target' => '_blank',
        ],
      ];
      $mlids = db_select('menu_links', 'ml')
        ->fields('ml', [
        'mlid',
      ])
        ->condition('ml.link_path', 'node/' . $node->nid)
        ->execute()
        ->fetchCol();
      $menu_list = [];
      if (!empty($mlids)) {
        foreach ($mlids as $mlid) {
          $link = menu_link_load($mlid);
          $menu_list[] = $link['menu_name'];
        }
      }
      $in_menu_text = !empty($menu_list) ? t('in menu: @menus', [
        '@menus' => implode(', ', $menu_list),
      ]) : '';
      $description['#items'][] = t('!title (!status) @menu', [
        '!title' => l($node->title, '/node/' . $node->nid, $options),
        '!status' => drupal_render($status),
        '@menu' => $in_menu_text,
      ]);
    }
    $content_discovery_description = drupal_render($description);
    if ($node->status) {
      $privacy_policy_description = t('This one looks good to go, but you need to verify that it has real and relevant content.');
    }
    else {
      $privacy_policy_description = t('We could not find a <em>published</em> “Privacy Policy” page.');
    }
  }
  else {
    $content_discovery_description = t('No nodes with the following terms have been found: "Privacy Policy", "Terms of Use", "About us" or "Impressum".');
    $privacy_policy_description = t('Please verify manually that such content of similar titles exists and has been published.');
  }
  $definitions['gdpr_checklist'] = [
    '#title' => t('GDPR Checklist'),
    '#path' => 'admin/config/gdpr/checklist',
    '#description' => t('GDPR Checklist'),
    '#help' => t('<p>Complete this checklist to make your site GDPR-compliant.</p>'),
    'getting_started' => [
      '#title' => t('Getting Started'),
      '#description' => t("<p>To begin your self-assessment process, it's highly recommended to take the following steps:</p>"),
      'responsability_agreement' => [
        '#title' => t("Yes, I agree that by using the <em>GDPR</em> module, I'm still accountable for personal data handling performed on the site."),
        '#description' => t('Responsibility Agreement: before the site owner starts the checklist process, they should acknowledge that installing and using this module pack does not mean sharing responsibility. Neither the Drupal Community nor module maintainers can guarantee full compliance with the GDPR regulations in case of a potential control.'),
      ],
      'recommended_resources' => [
        '#title' => t('Yes, I have read at least one of the following recommended resources from the list below'),
        'wiki_page' => [
          '#text' => t('GDPR Wikipedia page'),
          '#path' => t('https://en.wikipedia.org/wiki/General_Data_Protection_Regulation'),
        ],
        'summary' => [
          '#text' => t('Summary of the GDPR regulation'),
          '#path' => t('http://eur-lex.europa.eu/legal-content/EN/LSU/?uri=CELEX:32016R0679#document1'),
        ],
        'legislation' => [
          '#text' => t('The GDPR legislation'),
          '#path' => t('http://eur-lex.europa.eu/eli/reg/2016/679/oj'),
        ],
      ],
    ],
    'policies' => [
      '#title' => t('Policies and other measures'),
      '#description' => t('<p>Modules or libraries that already implement some of the needed features.</p>'),
      'data1' => [
        '#title' => t('Cookie policy'),
        '#description' => t('User needs to be informed when your site uses cookies to collect data.'),
        'handbook_page' => [
          '#text' => t('EU Cookie Compliance-module'),
          '#path' => 'https://www.drupal.org/project/eu_cookie_compliance',
        ],
      ],
    ],
    'content_related_suggestions' => [
      '#title' => t('Content related suggestions'),
      '#description' => t('<p>Automated search performed on content of site.</p>') . $content_discovery_description,
      'privacy_policy_page' => [
        '#title' => t("I have checked through the site's content and verified that a page containing <em>Privacy Policy</em> exists and has been published to this site."),
        '#description' => $privacy_policy_description,
      ],
      'privacy_policy_published' => [
        '#title' => t('I confirm the existing "Privacy Policy" has been published.'),
        '#description' => t('This <em>GDPR</em> module does not automatically affect publishing status of site content. Therefore, even if such information has been uploaded (regarding the previous point), then you must make sure that this content is available to visitors.'),
      ],
      'privacy_policy_in_menu' => [
        '#title' => t('I confirm the published "Privacy Policy" is included in at least one menu of the site.'),
        '#description' => t("To ensure best practice, it's strongly recommended to divulge personal data handling guidelines on the site by including a link to the guidelines page in a generally displayed menu in clear view on the site layout."),
      ],
    ],
    'site_features' => [
      '#title' => t('Site feature related suggestions'),
      '#description' => t('This group of checkpoints addresses various actions to be taken regarding site features.'),
      'external_traffic_measurement' => [
        '#title' => t("Yes, I'm aware there are external traffic measurement and user tracking services integrated on this site"),
        '#description' => t('As the owner and/or maintainer of the site I have, to the best of my knowledge, taken the necessary measures to set up these 3rd-party data collecting tools to ensure maximum compliance with GDPR regulations.') . _get_module_list('tracking'),
      ],
      'social_media_connections' => [
        '#title' => t("Yes, I'm aware there are connections established between this site and some social media platforms"),
        '#description' => t('As many social media platforms collects identifiable personal data, I acknowledge that the owner of this site is responsible for setting up these connections to ensure maximum compliance with GDPR regulations.') . _get_module_list('social'),
      ],
      'module_data_collection' => [
        '#title' => t('I confirm that all Drupal core, community, and custom modules have been revised as to not gather any unnecessary personal data of site visitors'),
        '#description' => t('Owner and/or maintainer of the site has to ensure that the modules listed below have been revised as to gather only necessary (i.e. not needed for provision of service) personal data of site visitors.') . _get_module_list('collect'),
      ],
      'user_role_permissions' => [
        '#title' => t('I have examined all aspects of displayed personal data, associated with this site, to ensure that all users can access only permitted types of information according to their role'),
        '#description' => t('Consider every use case in which Drupal fetches information from its database and exposes it to external parties. Bear in mind all possible scenarios extending beyond webpages, eg. RSS feeds, metadata and outgoing e-mail templates sent by the site.'),
      ],
    ],
    'configuration' => [
      '#title' => t('Configuration'),
      '#description' => t('<p>Things you can can do by just making changes in your site configuration</p>'),
      'cancel_account' => [
        '#title' => t('Allow users to cancel their own user account'),
        'permission' => [
          '#text' => t('Permissions for "Cancel own user account"'),
          '#path' => '/admin/people/permissions',
          '#options' => [
            'fragment' => 'module-user',
          ],
        ],
      ],
      'delete_data' => [
        '#title' => t('Users need to be able to request for removal of all their personal data.'),
        'account_config' => [
          '#text' => t('Account configuration for "Delete the account and make its content belong to the Anonymous user"'),
          '#path' => '/admin/config/people/accounts',
        ],
      ],
    ],
    'beyond_website_management' => [
      '#title' => t('Beyond website management'),
      '#description' => t('The scope of GDPR is not clearly defined, stretching far beyond IT and legal aspects.'),
      'legal_adviser_consulted' => [
        '#title' => t('I have found and consulted with a legal adviser'),
        '#description' => t('It is strongly suggested to have a complete overview of the internal data handling workflows within the organisation represented by the website. An experienced legal adviser can guide your organisation through this transformation process.'),
      ],
      'gdpr_modules' => [
        '#title' => t('I have enabled all features of the GDPR module'),
        '#description' => _get_gdpr_module_description(),
      ],
      'notice_upon_breach' => [
        '#title' => t('I declare that I am able to notify the supervisory authority within 72 hours in the case of a personal data breach'),
        '#description' => t('Referring to Article 33 of the regulation, site owner/maintainer as a data controller must have a contingency plan in place in the event of any personal data breach incident.'),
      ],
      'logging_responsability' => [
        '#title' => t('I confirm that I have understood that the responsibility of personal data logging is dependent on the size of my organisation as described below:'),
        '#description' => _get_logging_responsability_description(),
      ],
    ],
  ];
  return $definitions;
}

/**
 * Implements a page callback to render a user's GDPR data collection page.
 *
 * @param \stdClass $user
 *   The user entity.
 *
 * @return string
 *   The rendered page.
 */
function gdpr_collected_user_data(\stdClass $user) {
  drupal_set_title(t('Data stored about you'));
  $user_data = gdpr_get_user_data($user);
  $output = theme('user_data_page', [
    'user_data' => $user_data,
  ]);
  return $output;
}

/**
 * Implements an access callback to a user's GDPR data collection page.
 *
 * @param \stdClass $user
 *   The user entity.
 *
 * @return bool
 *   Whether or not the current user has access.
 */
function gdpr_collected_user_data_access(\stdClass $user) {
  return $GLOBALS['user']->uid == $user->uid;
}

/**
 * Gets all collected data for the given user.
 *
 * @param \stdClass $user
 *   The user entity.
 *
 * @return array
 *   Keys are data machine names, and values the collected data itself.
 */
function gdpr_get_user_data(\stdClass $user) {
  $user_data = [];
  foreach ($user as $key => $value) {
    if (empty($value)) {
      continue;
    }
    if (is_scalar($value)) {
      $user_data[$key] = $value;
    }
    elseif (is_array($value)) {
      $scalar_list = TRUE;
      foreach ($value as $item) {
        if (!is_scalar($item)) {
          $scalar_list = FALSE;
          break;
        }
      }
      if ($scalar_list) {
        $user_data[$key] = implode(', ', $value);
      }
    }
  }
  $user_data['pass'] = gdpr_star_value($user_data['pass']);
  ksort($user_data);
  return $user_data;
}

/**
 * Censors a string by replacing all characters with asterisks.
 *
 * @param string $value
 *   The value to censor.
 *
 * @return string
 *   The censored value.
 */
function gdpr_star_value($value) {
  return str_repeat("*", strlen($value));
}

/**
 * Returns a HTML makrup for logging_responsability checkpoint description.
 *
 * @return string
 *   HTML markup string
 */
function _get_logging_responsability_description() {
  $logging_items = [
    '#theme' => 'item_list',
    '#items' => [
      t('Has fewer than 250 employees the processing it carries out is likely no result in a risk to the rights and freedoms of data subjects, the processing is only occasional and the processing does not includes special categories of data (Article 30/5) and as such my organisation is not required to create records of processing acitivities'),
      t('My organisation has 250 or more employees and records of processing activities have been prepared according to Article 30 of the regulation'),
    ],
  ];
  return drupal_render($logging_items);
}

/**
 * Returns a HTML makrup for gdpr_modules checkpoint description.
 *
 * @return string
 *   HTML markup string
 */
function _get_gdpr_module_description() {
  $all_modules = _get_modules();
  $gdpr_module_names = [
    'gdpr_dump',
  ];
  $gdpr_modules_list = [
    '#theme' => 'item_list',
    '#items' => [],
  ];
  $module_statuses = _get_module_statuses();
  $color_classes = [
    -1 => 'admin-missing',
    0 => 'admin-disabled',
    1 => 'admin-enabled',
  ];
  foreach ($gdpr_module_names as $module_name) {
    if (isset($all_modules[$module_name])) {
      if (!$all_modules[$module_name]['status']) {
        $status_class = $color_classes[$all_modules[$module_name]['status']];
        $status = [
          '#prefix' => '<span class="' . $status_class . '">',
          '#suffix' => '</span>',
          '#markup' => $module_statuses[$all_modules[$module_name]['status']],
        ];
        $gdpr_modules_list['#items'][] = t('@title (!status)', [
          '@title' => $all_modules[$module_name]['name'],
          '!status' => drupal_render($status),
        ]);
      }
    }
    else {
      $status_class = $color_classes[-1];
      $status = [
        '#prefix' => '<span class="' . $status_class . '">',
        '#suffix' => '</span>',
        '#markup' => $module_statuses[-1],
      ];
      $gdpr_modules_list['#items'][] = t('@title (!status)', [
        '@title' => $module_name,
        '!status' => drupal_render($status),
      ]);
    }
  }
  $gdpr_modules = drupal_render($gdpr_modules_list);
  $options = [
    'absolute' => TRUE,
  ];
  $link = l(t('Enable them on Modules admin page'), '/admin/modules', $options);
  if (!empty($gdpr_modules_list['#items'])) {
    $text = t("In order to have a complete overview of the site functionalities, it's recommended to enable the following features as well:");
    return $text . $gdpr_modules . $link;
  }
  else {
    return '';
  }
}

/**
 * Returns an array of module statuses.
 *
 * @return array
 *   Module statuses array
 */
function _get_module_statuses() {
  return [
    -1 => t('missing'),
    0 => t('disabled'),
    1 => t('enabled'),
  ];
}

/**
 * Searches for gdpr related nodes. i.e.: Privacy Policy, Terms of use.
 *
 * @return array
 *   Nodes array
 */
function gdpr_search_content() {
  if ($cached_content = cache_get('gdpr_content', 'cache')) {
    $nids = $cached_content->data;
  }
  else {

    // TODO: Localization.
    $nids = [];
    if (module_exists('node')) {
      $pattern = '(privacy( +)policy)|(terms( +)of( +)use)|(about( +)us)|(impressum)';
      $query = new EntityFieldQuery();
      $results = $query
        ->entityCondition('entity_type', 'node')
        ->propertyCondition('title', $pattern, 'RLIKE')
        ->execute();
      if (!empty($results['node'])) {
        $nids = array_keys($results['node']);
      }
    }
    cache_set('gdpr_content', $nids, 'cache', CACHE_TEMPORARY);
  }
  return node_load_multiple($nids);
}

/**
 * Returns a HTML makrup for data_collecting modules checkpoint description.
 *
 * @param string $type
 *   Type of modules to search for.
 *
 * @return string
 *   HTML markup string
 */
function _get_module_list($type) {
  $c_modules = _get_module_list_by_type($type);
  $modules_list = [
    '#theme' => 'item_list',
    '#items' => [],
  ];
  $module_statuses = _get_module_statuses();
  $all_modules = _get_modules();
  $color_classes = [
    -1 => 'admin-enabled',
    0 => 'admin-enabled',
    1 => 'admin-missing',
  ];
  foreach ($c_modules as $c_module) {
    if (isset($all_modules[$c_module])) {
      $status_class = $color_classes[$all_modules[$c_module]['status']];
      $status = [
        '#prefix' => '<span class="' . $status_class . '">',
        '#suffix' => '</span>',
        '#markup' => $module_statuses[$all_modules[$c_module]['status']],
      ];
      $modules_list['#items'][] = t('@title (!status)', [
        '@title' => $all_modules[$c_module]['name'],
        '!status' => drupal_render($status),
      ]);
    }
  }
  return drupal_render($modules_list);
}

/**
 * Returns array of module machine_names by type.
 *
 * @param string $type
 *   The type of modules to swarch for.
 *
 * @return array
 *   Array of module machine_names
 */
function _get_module_list_by_type($type) {
  $list = [];
  switch ($type) {
    case 'collect':
      $list = [
        'contact',
        'webform',
        'geoip',
      ];
      break;
    case 'tracking':
      $list = [
        'google_analytics',
        'google_tag',
        'piwik',
        'hotjar',
        'recaptcha',
        'site_verify',
        'adsense',
        'yandex_metrics',
      ];
      break;
    case 'social':
      $list = [
        'oauth',
        'sharethis',
        'addtoany',
        'cas',
        'fb_likebox',
        'easy_social',
      ];
      break;
  }
  return $list;
}

/**
 * Returns cached module data from module info files.
 *
 * @return array
 *   Array of module data
 */
function _get_modules() {
  $cache = cache_get('modules_list');
  if ($cache) {
    return $cache->data;
  }
  else {
    $modules_list_raw = system_rebuild_module_data();
    $modules_list = [];
    foreach ($modules_list_raw as $module) {
      $modules_list[$module->name] = [
        'status' => $module->status,
        'name' => $module->info['name'],
      ];
    }
    cache_set('modules_list', $modules_list);
    return $modules_list;
  }
}

Functions

Namesort descending Description
gdpr_checklistapi_checklist_info Implements hook_checklistapi_checklist_info().
gdpr_collected_user_data Implements a page callback to render a user's GDPR data collection page.
gdpr_collected_user_data_access Implements an access callback to a user's GDPR data collection page.
gdpr_get_user_data Gets all collected data for the given user.
gdpr_help Implements hook_help().
gdpr_menu Implements hook_menu().
gdpr_search_content Searches for gdpr related nodes. i.e.: Privacy Policy, Terms of use.
gdpr_star_value Censors a string by replacing all characters with asterisks.
gdpr_theme Implements hook_theme().
_get_gdpr_module_description Returns a HTML makrup for gdpr_modules checkpoint description.
_get_logging_responsability_description Returns a HTML makrup for logging_responsability checkpoint description.
_get_modules Returns cached module data from module info files.
_get_module_list Returns a HTML makrup for data_collecting modules checkpoint description.
_get_module_list_by_type Returns array of module machine_names by type.
_get_module_statuses Returns an array of module statuses.