diff --git a/composer.json b/composer.json
index ace483f..449f2a9 100644
--- a/composer.json
+++ b/composer.json
@@ -11,6 +11,9 @@
],
"require": {
"drupal/checklistapi": "^1.0",
- "drupal/entity_reference_revisions": "^1.4"
+ "drupal/entity_reference_revisions": "^1.4",
+ "drupal/message": "^1.0@RC",
+ "drupal/token": "^1.1",
+ "drupal/views_data_export": "^1.0@alpha"
}
}
diff --git a/gdpr.info.yml b/gdpr.info.yml
index d0f4226..2dc9fa8 100644
--- a/gdpr.info.yml
+++ b/gdpr.info.yml
@@ -6,5 +6,5 @@ configure: checklistapi.checklists.gdpr_checklist
package: General Data Protection Regulation
dependencies:
- - drupal:checklistapi
- - drupal:message
\ No newline at end of file
+ - checklistapi:checklistapi
+ - message:message
diff --git a/gdpr.module b/gdpr.module
index 604fa69..dbe926d 100644
--- a/gdpr.module
+++ b/gdpr.module
@@ -489,7 +489,7 @@ function gdpr_toolbar() {
// Just add the root menu item.
// GDPR Tasks and GDPR Consent modules
// will modify this in hook_toolbar_alter.
- if ($user->hasPermission('view gdpr tasks') || $user->hasPermission('manage gdpr agreements')) {
+ if ($user->hasPermission('view task entities') || $user->hasPermission('manage gdpr agreements')) {
$items['gdpr'] += [
'#type' => 'toolbar_item',
'tab' => [
diff --git a/gdpr.routing.yml b/gdpr.routing.yml
index 951f5ef..be322fb 100644
--- a/gdpr.routing.yml
+++ b/gdpr.routing.yml
@@ -4,7 +4,7 @@ gdpr.collected_user_data:
_title: 'Data stored about you'
_controller: '\Drupal\gdpr\Controller\UserController::collectedData'
requirements:
- _custom_access: '\Drupal\gdpr\Controller\UserController::access'
+ _custom_access: '\Drupal\gdpr\Controller\UserController::accessCollectedData'
options:
parameters:
user:
diff --git a/modules/gdpr_consent/gdpr_consent.info.yml b/modules/gdpr_consent/gdpr_consent.info.yml
index 0c26ccc..3acd6b0 100644
--- a/modules/gdpr_consent/gdpr_consent.info.yml
+++ b/modules/gdpr_consent/gdpr_consent.info.yml
@@ -6,6 +6,6 @@ package: General Data Protection Regulation
dependencies:
- gdpr:gdpr
- - token
- - message
- - entity_reference_revisions
\ No newline at end of file
+ - token:token
+ - message:message
+ - entity_reference_revisions:entity_reference_revisions
diff --git a/modules/gdpr_consent/src/Controller/ConsentAgreementController.php b/modules/gdpr_consent/src/Controller/ConsentAgreementController.php
index a66a82b..de5d96c 100644
--- a/modules/gdpr_consent/src/Controller/ConsentAgreementController.php
+++ b/modules/gdpr_consent/src/Controller/ConsentAgreementController.php
@@ -97,7 +97,7 @@ public function revisionOverview($gdpr_consent_agreement) {
$account = $this->currentUser();
$storage = $this->entityManager()->getStorage('gdpr_consent_agreement');
- $build['#title'] = $this->t('Revisions for %title', ['%title' => $agreement->label()]);
+ $build['#title'] = $this->t('Revisions for %title', ['%title' => $agreement->title->value]);
$header = [$this->t('Revision'), $this->t('Operations')];
$revert_permission = $account->hasPermission('create gdpr agreements');
diff --git a/modules/gdpr_consent/src/Entity/ConsentAgreement.php b/modules/gdpr_consent/src/Entity/ConsentAgreement.php
index e200520..3d91f77 100644
--- a/modules/gdpr_consent/src/Entity/ConsentAgreement.php
+++ b/modules/gdpr_consent/src/Entity/ConsentAgreement.php
@@ -256,6 +256,20 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
->setDisplayConfigurable('form', TRUE)
->setDisplayConfigurable('view', TRUE);
+ $fields['notes'] = BaseFieldDefinition::create('string_long')
+ ->setLabel(t('Notes'))
+ ->setRevisionable(TRUE)
+ ->setDescription(t('This should contain the rationale behind the agreement.'))
+ ->setDisplayOptions('view', [
+ 'type' => 'textarea',
+ ])
+ ->setDisplayOptions('form', [
+ 'type' => 'textarea',
+ 'weight' => 4,
+ ])
+ ->setDisplayConfigurable('form', TRUE)
+ ->setDisplayConfigurable('view', TRUE);
+
$fields['status'] = BaseFieldDefinition::create('boolean')
->setLabel(t('Publishing status'))
->setDescription(t('A boolean indicating whether the Consent Agreement is published.'))
@@ -317,5 +331,4 @@ public function __toString() {
return $this->get('title')->getString();
}
-
-}
\ No newline at end of file
+}
diff --git a/modules/gdpr_dump/gdpr_dump.info.yml b/modules/gdpr_dump/gdpr_dump.info.yml
index bf17fbf..dc541b2 100644
--- a/modules/gdpr_dump/gdpr_dump.info.yml
+++ b/modules/gdpr_dump/gdpr_dump.info.yml
@@ -6,4 +6,4 @@ configure: gdpr_dump.settings
package: General Data Protection Regulation
dependencies:
- - drupal:gdpr
+ - gdpr:gdpr
diff --git a/modules/gdpr_fields/config/schema/gdpr_field.schema.yml b/modules/gdpr_fields/config/schema/gdpr_field.schema.yml
index fca48dd..21681bc 100644
--- a/modules/gdpr_fields/config/schema/gdpr_field.schema.yml
+++ b/modules/gdpr_fields/config/schema/gdpr_field.schema.yml
@@ -7,4 +7,4 @@ field.field.*.*.*.third_party.gdpr_fields:
label: 'Whether this field is user sensitive or not.'
gdpr_fields_rtf:
type: string
- label: 'Whether this field is user sensitive or not.'
\ No newline at end of file
+ label: 'Whether this field is user sensitive or not.'
diff --git a/modules/gdpr_fields/config/schema/gdpr_fields.schema.yml b/modules/gdpr_fields/config/schema/gdpr_fields.schema.yml
new file mode 100644
index 0000000..9df259e
--- /dev/null
+++ b/modules/gdpr_fields/config/schema/gdpr_fields.schema.yml
@@ -0,0 +1,30 @@
+gdpr_fields.gdpr_fields_config.*:
+ type: config_entity
+ label: 'GDPR Field Config'
+ mapping:
+ id:
+ type: string
+ label: 'ID'
+ bundles:
+ type: sequence
+ label: 'Entity Bundles'
+ sequence:
+ type: sequence
+ label: 'Fields'
+ sequence:
+ type: mapping
+ mapping:
+ bundle:
+ type: string
+ name:
+ type: string
+ enabled:
+ type: boolean
+ rta:
+ type: string
+ rtf:
+ type: string
+ sanitizer:
+ type: string
+ notes:
+ type: string
diff --git a/modules/gdpr_fields/css/field-list.css b/modules/gdpr_fields/css/field-list.css
new file mode 100644
index 0000000..98d8d44
--- /dev/null
+++ b/modules/gdpr_fields/css/field-list.css
@@ -0,0 +1,24 @@
+.notes:before {
+ content: attr(data-icon);
+ font-weight: bold;
+ display: inline-block;
+ width: 1em;
+ font-size: 2em;
+}
+
+.notes + div {
+ float: right;
+ margin-top: -3em;
+ position: absolute;
+ z-index: 3;
+ display: none;
+ border: 1px solid #ccc;
+ background: #fff;
+ padding: 10px;
+ min-width: 200px;
+}
+
+.notes:hover + div,
+.notes + div:hover {
+ display: inline-block;
+}
\ No newline at end of file
diff --git a/modules/gdpr_fields/gdpr_fields.libraries.yml b/modules/gdpr_fields/gdpr_fields.libraries.yml
new file mode 100644
index 0000000..04b780f
--- /dev/null
+++ b/modules/gdpr_fields/gdpr_fields.libraries.yml
@@ -0,0 +1,4 @@
+field-list:
+ css:
+ theme:
+ css/field-list.css: {}
\ No newline at end of file
diff --git a/modules/gdpr_fields/gdpr_fields.module b/modules/gdpr_fields/gdpr_fields.module
index 72f0af5..6d35cec 100644
--- a/modules/gdpr_fields/gdpr_fields.module
+++ b/modules/gdpr_fields/gdpr_fields.module
@@ -7,6 +7,8 @@
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
+use Drupal\gdpr_fields\Entity\GdprFieldConfigEntity;
+use Drupal\gdpr_fields\Form\GdprFieldSettingsForm;
/**
* Implements hook_toolbar_alter().
@@ -52,72 +54,17 @@ function gdpr_fields_form_field_config_edit_form_alter(&$form, FormStateInterfac
'#open' => TRUE,
];
- $form['field']['gdpr_fields']['gdpr_fields_enabled'] = [
- '#type' => 'checkbox',
- '#title' => t('This is a GDPR field'),
- '#default_value' => $field->getThirdPartySetting('gdpr_fields', 'gdpr_fields_enabled', FALSE),
- ];
-
- $form['field']['gdpr_fields']['gdpr_fields_rta'] = [
- '#type' => 'select',
- '#title' => t('Right to access'),
- '#options' => [
- 'inc' => 'Included',
- 'maybe' => 'Maybe',
- 'no' => 'Not',
- ],
- '#default_value' => $field->getThirdPartySetting('gdpr_fields', 'gdpr_fields_rta', 'no'),
- '#states' => [
- 'visible' => [
- ':input[name="gdpr_fields_enabled"]' => [
- 'checked' => TRUE,
- ],
- ],
- ],
- ];
-
- $form['field']['gdpr_fields']['gdpr_fields_rtf'] = [
- '#type' => 'select',
- '#title' => t('Right to be forgotten'),
- '#options' => [
- 'anonymise' => 'Anonymise',
- 'remove' => 'Remove',
- 'maybe' => 'Maybe',
- 'no' => 'Not',
- ],
- '#default_value' => $field->getThirdPartySetting('gdpr_fields', 'gdpr_fields_rtf', 'no'),
- '#states' => [
- 'visible' => [
- ':input[name="gdpr_fields_enabled"]' => [
- 'checked' => TRUE,
- ],
- ],
- ],
- ];
-
- $sanitizer_options = ['' => ''] + array_map(function ($s) {
- return $s['label'];
- }, $sanitizer_definitions);
+ GdprFieldSettingsForm::buildFormFields($form['field']['gdpr_fields'], $field->getTargetEntityTypeId(), $field->getTargetBundle(), $field->getName());
- $form['field']['gdpr_fields']['gdpr_fields_sanitizer'] = [
- '#type' => 'select',
- '#title' => t('Sanitizer to use'),
- '#options' => $sanitizer_options,
- '#default_value' => $field->getThirdPartySetting('gdpr_fields', 'gdpr_fields_sanitizer', ''),
- '#states' => [
- 'visible' => [
- ':input[name="gdpr_fields_enabled"]' => ['checked' => TRUE],
- ':input[name="gdpr_fields_rtf"]' => ['value' => 'anonymise'],
- ],
- ],
- ];
-
$form['actions']['submit']['#submit'][] = 'gdpr_fields_form_field_config_edit_form_submit';
}
/**
* Form submission handler for gdpr_fields_form_field_config_edit_form_alter.
*
+ * This version of the GDPR fields form is embedded on the standard field
+ * settings page.
+ *
* @param array $form
* The form array.
* @param \Drupal\Core\Form\FormStateInterface $form_state
@@ -128,21 +75,16 @@ function gdpr_fields_form_field_config_edit_form_submit(array $form, FormStateIn
$field = $form_state->getFormObject()->getEntity();
$form_fields = &$form_state->getValues();
- // If the private option is checked, update settings.
- if ($form_fields['gdpr_fields_enabled']) {
- $field->setThirdPartySetting('gdpr_fields', 'gdpr_fields_enabled', TRUE);
- $field->setThirdPartySetting('gdpr_fields', 'gdpr_fields_rta', $form_fields['gdpr_fields_rta']);
- $field->setThirdPartySetting('gdpr_fields', 'gdpr_fields_rtf', $form_fields['gdpr_fields_rtf']);
- $field->setThirdPartySetting('gdpr_fields', 'gdpr_fields_sanitizer', $form_fields['gdpr_fields_sanitizer']);
- $field->save();
- }
- else {
- $field->unsetThirdPartySetting('gdpr_fields', 'gdpr_fields_enabled');
- $field->unsetThirdPartySetting('gdpr_fields', 'gdpr_fields_rta');
- $field->unsetThirdPartySetting('gdpr_fields', 'gdpr_fields_rtf');
- $field->unsetThirdPartySetting('gdpr_fields', 'gdpr_fields_sanitizer');
-
- $field->save();
- }
-
+ $config = GdprFieldConfigEntity::load($field->getTargetEntityTypeId()) ?? GdprFieldConfigEntity::create(['id' => $field->getTargetEntityTypeId()]);
+ $config->setField(
+ $field->getTargetBundle(),
+ $field->getName(), [
+ 'enabled' => $form_state->getValue('gdpr_enabled'),
+ 'rta' => $form_state->getValue('gdpr_rta'),
+ 'rtf' => $form_state->getValue('gdpr_rtf'),
+ 'sanitizer' => $form_state->getValue('gdpr_sanitizer'),
+ 'notes' => $form_state->getValue('gdpr_notes'),
+ ]
+ );
+ $config->save();
}
diff --git a/modules/gdpr_fields/gdpr_fields.routing.yml b/modules/gdpr_fields/gdpr_fields.routing.yml
index 0b79a36..b0df8b9 100644
--- a/modules/gdpr_fields/gdpr_fields.routing.yml
+++ b/modules/gdpr_fields/gdpr_fields.routing.yml
@@ -1,7 +1,15 @@
gdpr_fields.fields_list:
- path: '/admin/reports/fields/gdpr-fields'
+ path: '/admin/reports/fields/gdpr-fields/{mode}'
defaults:
_controller: '\Drupal\gdpr_fields\Controller\GDPRController::fieldsList'
_title: 'Used in GDPR'
+ mode: 'all'
requirements:
_permission: 'view gdpr fields'
+
+gdpr_fields.edit_field:
+ path: '/admin/gdpr/fields/{entity_type}/{bundle_name}/{field_name}'
+ defaults:
+ _form: 'Drupal\gdpr_fields\Form\GdprFieldSettingsForm'
+ requirements:
+ _permission: 'view gdpr fields'
\ No newline at end of file
diff --git a/modules/gdpr_fields/src/Controller/GDPRController.php b/modules/gdpr_fields/src/Controller/GDPRController.php
index f5a1586..9b89288 100644
--- a/modules/gdpr_fields/src/Controller/GDPRController.php
+++ b/modules/gdpr_fields/src/Controller/GDPRController.php
@@ -46,37 +46,37 @@ public static function create(ContainerInterface $container) {
* @return array
* The Views plugins report page.
*/
- public function fieldsList() {
+ public function fieldsList($mode) {
$output = [];
$entities = [];
+ $include_not_configured = $mode == 'all';
$this->collector->getEntities($entities);
+ $output['filter'] = $this->formBuilder()->getForm('Drupal\gdpr_fields\Form\GdprFieldFilterForm');
+ $output['#attached']['library'][] = 'gdpr_fields/field-list';
+
+
foreach ($entities as $entity_type => $bundles) {
- $output[$entity_type] = array(
+ $output[$entity_type] = [
'#type' => 'details',
'#title' => t($entity_type),
- '#description' => t('@configure entity @entity_type for GDPR.', [
- // @todo Create ability to exclude entity type from GDPR in configuration.
- '@configure' => Link::fromTextAndUrl('Configure', Url::fromUri('internal:/'))->toString(),
- '@entity_type' => ucfirst($entity_type),
- ]),
'#open' => TRUE,
- );
+ ];
if (count($bundles) > 1) {
foreach ($bundles as $bundle_id) {
- $output[$entity_type][$bundle_id] = array(
+ $output[$entity_type][$bundle_id] = [
'#type' => 'details',
'#title' => t($bundle_id),
'#open' => TRUE,
- );
- $output[$entity_type][$bundle_id]['fields'] = $this->buildFieldTable($entity_type, $bundle_id);
+ ];
+ $output[$entity_type][$bundle_id]['fields'] = $this->buildFieldTable($entity_type, $bundle_id, $include_not_configured);
}
}
else {
// Don't add another collapsible wrapper around single bundle entities.
$bundle_id = reset($bundles);
- $output[$entity_type][$bundle_id]['fields'] = $this->buildFieldTable($entity_type, $bundle_id);
+ $output[$entity_type][$bundle_id]['fields'] = $this->buildFieldTable($entity_type, $bundle_id, $include_not_configured);
}
}
@@ -94,18 +94,49 @@ public function fieldsList() {
* @return array
* Renderable array for field list table.
*/
- protected function buildFieldTable($entity_type, $bundle_id) {
- $rows = $this->collector->listFields($entity_type, $bundle_id);
+ protected function buildFieldTable($entity_type, $bundle_id, $include_not_configured) {
+ $rows = $this->collector->listFields($entity_type, $bundle_id, $include_not_configured);
// Sort rows by field name.
ksort($rows);
- return [
+ $table = [
'#type' => 'table',
- '#header' => [t('Name'), t('Type'), t('Right to access'), t('Right to be forgotten'), ''],
- '#rows' => $rows,
+ '#header' => [t('Name'), t('Type'), t('Right to access'), t('Right to be forgotten'), t('Notes'), ''],
+ // '#rows' => $rows,
'#sticky' => TRUE,
'#empty' => t('There are no GDPR fields for this entity.'),
];
+
+ $i = 0;
+ foreach ($rows as $row) {
+ $table[$i]['title'] = [
+ '#plain_text' => $row['title'],
+ ];
+
+ $table[$i]['type'] = [
+ '#plain_text' => $row['type'],
+ ];
+
+ $table[$i]['gdpr_rta'] = [
+ '#plain_text' => $row['gdpr_rta'],
+ ];
+
+ $table[$i]['gdpr_rtf'] = [
+ '#plain_text' => $row['gdpr_rtf'],
+ ];
+
+ $table[$i]['notes'] = [
+ '#markup' => empty($row['notes']) ? '' : '
' . $row['notes'] . '
',
+ ];
+
+ $table[$i]['edit'] = [
+ '#markup' => !empty($row['edit']) ? $row['edit']->toString() : '',
+ ];
+
+ $i++;
+ }
+
+ return $table;
}
/**
diff --git a/modules/gdpr_fields/src/Entity/GdprField.php b/modules/gdpr_fields/src/Entity/GdprField.php
new file mode 100644
index 0000000..ad7147b
--- /dev/null
+++ b/modules/gdpr_fields/src/Entity/GdprField.php
@@ -0,0 +1,150 @@
+bundle = $bundle;
+ $this->name = $name;
+ }
+
+ /**
+ * Creates a GdprField instance based on array data from the config entity.
+ *
+ * @param array $values
+ * The undlerying data.
+ *
+ * @return \Drupal\gdpr_fields\Entity\GdprField
+ * The field metadata instance.
+ */
+ public static function create(array $values) {
+ $field = new static($values['bundle'], $values['name']);
+ $field->rtf = $values['rtf'];
+ $field->rta = $values['rta'];
+ $field->enabled = $values['enabled'];
+ $field->sanitizer = $values['sanitizer'];
+ $field->notes = $values['notes'];
+ $field->configured = TRUE;
+ return $field;
+ }
+
+ /**
+ * Gets the RTF description.
+ *
+ * @return string
+ * The description.
+ */
+ public function rtfDescription() {
+ switch ($this->rtf) {
+ case 'anonymise':
+ return 'Anonymise';
+
+ case 'remove':
+ return 'Remove';
+
+ case 'maybe':
+ return 'Maybe';
+
+ case 'no':
+ return 'Not Included';
+
+ default:
+ return 'Not Configured';
+
+ }
+ }
+
+ /**
+ * Gets the RTA description.
+ *
+ * @return string
+ * The description.
+ */
+ public function rtaDescription() {
+ switch ($this->rta) {
+ case 'inc':
+ return 'Included';
+
+ case 'maybe':
+ return 'Maybe';
+
+ case 'no':
+ return 'Not Included';
+
+ default:
+ return 'Not Configured';
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/gdpr_fields/src/Entity/GdprFieldConfigEntity.php b/modules/gdpr_fields/src/Entity/GdprFieldConfigEntity.php
new file mode 100644
index 0000000..b844495
--- /dev/null
+++ b/modules/gdpr_fields/src/Entity/GdprFieldConfigEntity.php
@@ -0,0 +1,93 @@
+ $value) {
+ $this->bundles[$bundle][$field_name][$key] = $value;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Gets field metadata.
+ *
+ * @param string $bundle
+ * Bundle.
+ * @param string $field_name
+ * Field name.
+ *
+ * @return \Drupal\gdpr_fields\Entity\GdprField
+ * Field metadata.
+ */
+ public function getField($bundle, $field_name) {
+ $bundle_fields = array_filter($this->bundles, function ($key) use ($bundle) {
+ return $key == $bundle;
+ }, ARRAY_FILTER_USE_KEY);
+
+ if (count($bundle_fields)) {
+ $result = array_filter($bundle_fields[$bundle], function ($f) use ($field_name) {
+ return $f['name'] == $field_name;
+ });
+
+ if (count($result)) {
+ return GdprField::create(reset($result));
+ }
+ }
+
+ return new GdprField($bundle, $field_name);
+ }
+
+}
diff --git a/modules/gdpr_fields/src/Form/GdprFieldFilterForm.php b/modules/gdpr_fields/src/Form/GdprFieldFilterForm.php
new file mode 100644
index 0000000..45aa94e
--- /dev/null
+++ b/modules/gdpr_fields/src/Form/GdprFieldFilterForm.php
@@ -0,0 +1,49 @@
+getParameter('mode') == 'all' ? 1 : 0;
+
+ $form['filter'] = [
+ '#type' => 'checkbox',
+ '#title' => 'Include Not Configured',
+ '#default_value' => $filter_value,
+ ];
+
+ $form['submit'] = [
+ '#type' => 'submit',
+ '#value' => $this->t('Apply'),
+ '#button_type' => 'primary',
+ '#name' => 'Apply',
+ ];
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ $mode = $form_state->getValue('filter') == '1' ? 'all' : 'configured';
+ $form_state->setRedirect('gdpr_fields.fields_list', ['mode' => $mode]);
+ }
+}
\ No newline at end of file
diff --git a/modules/gdpr_fields/src/Form/GdprFieldSettingsDeleteForm.php b/modules/gdpr_fields/src/Form/GdprFieldSettingsDeleteForm.php
new file mode 100644
index 0000000..e3c64d7
--- /dev/null
+++ b/modules/gdpr_fields/src/Form/GdprFieldSettingsDeleteForm.php
@@ -0,0 +1,27 @@
+t('Are you sure you want to remove the GDPR settings from this field?');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCancelUrl() {
+ return new Url('gdpr_fields.fields_list');
+ }
+}
\ No newline at end of file
diff --git a/modules/gdpr_fields/src/Form/GdprFieldSettingsForm.php b/modules/gdpr_fields/src/Form/GdprFieldSettingsForm.php
new file mode 100644
index 0000000..f803362
--- /dev/null
+++ b/modules/gdpr_fields/src/Form/GdprFieldSettingsForm.php
@@ -0,0 +1,307 @@
+entityFieldManager = $entity_field_manager;
+ $this->entityTypeManager = $entity_type_manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('entity_field.manager'),
+ $container->get('entity_type.manager')
+ );
+ }
+
+ /**
+ * Gets the configuration for an entity/bundle/field.
+ *
+ * @param string $entity_type
+ * Entity type.
+ * @param string $bundle
+ * Bundle.
+ * @param string $field_name
+ * Field.
+ *
+ * @return \Drupal\gdpr_fields\Entity\GdprField
+ * Field metadata.
+ */
+ private static function getConfig($entity_type, $bundle, $field_name): GdprField {
+ $config = GdprFieldConfigEntity::load($entity_type) ?? GdprFieldConfigEntity::create(['id' => $entity_type]);
+ $field_config = $config->getField($bundle, $field_name);
+ return $field_config;
+ }
+
+ /**
+ * Sets the GDPR settings for a field.
+ *
+ * @param string $entity_type
+ * Entity type.
+ * @param string $bundle
+ * Bundle.
+ * @param string $field_name
+ * Field.
+ * @param boolean $enabled
+ * Whether GDPR is enabled for this field
+ * @param string $rta
+ * Right to Access setting
+ * @param string $rtf
+ * Right to be forgotten
+ * @param string $sanitizer
+ * Sanitizer to use.
+ * @param string $notes
+ * Notes
+ *
+ * @return \Drupal\gdpr_fields\Entity\GdprFieldConfigEntity
+ * The config entity.
+ */
+ private static function setConfig($entity_type, $bundle, $field_name, $enabled, $rta, $rtf, $sanitizer, $notes) {
+ $config = GdprFieldConfigEntity::load($entity_type) ?? GdprFieldConfigEntity::create(['id' => $entity_type]);
+ $config->setField($bundle, $field_name, [
+ 'enabled' => $enabled,
+ 'rta' => $rta,
+ 'rtf' => $rtf,
+ 'sanitizer' => $sanitizer,
+ 'notes' => $notes,
+ ]);
+ return $config;
+ }
+
+ /**
+ * Returns a unique string identifying the form.
+ *
+ * @return string
+ * The unique string identifying the form.
+ */
+ public function getFormId() {
+ return 'gdpr_fields_edit_field_form';
+ }
+
+ /**
+ * Form constructor.
+ *
+ * @param array $form
+ * An associative array containing the structure of the form.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The current state of the form.
+ *
+ * @return array
+ * The form structure.
+ */
+ public function buildForm(array $form, FormStateInterface $form_state, $entity_type = NULL, $bundle_name = NULL, $field_name = NULL) {
+ if (empty($entity_type) || empty($bundle_name) || empty($field_name)) {
+ $this->messenger()->addWarning('Could not load field.');
+ return [];
+ }
+
+ $field_defs = $this->entityFieldManager->getFieldDefinitions($entity_type, $bundle_name);
+
+ if (!array_key_exists($field_name, $field_defs)) {
+ $this->messenger()->addWarning("The field $field_name does not exist.");
+ return [];
+ }
+ $field_def = $field_defs[$field_name];
+ $form['#title'] = 'GDPR Settings for ' . $field_def->getLabel();
+
+ static::buildFormFields($form, $entity_type, $bundle_name, $field_name);
+
+
+ $form['entity_type'] = [
+ '#type' => 'hidden',
+ '#default_value' => $entity_type,
+ ];
+
+ $form['bundle'] = [
+ '#type' => 'hidden',
+ '#default_value' => $bundle_name,
+ ];
+
+ $form['field_name'] = [
+ '#type' => 'hidden',
+ '#default_value' => $field_name,
+ ];
+
+ $form['actions'] = [
+ '#type' => 'actions',
+ 'submit' => [
+ '#type' => 'submit',
+ '#value' => $this->t('Save'),
+ '#button_type' => 'primary',
+ '#name' => 'Save',
+ ],
+ 'submit_cancel' => [
+ '#type' => 'submit',
+ '#weight' => 99,
+ '#value' => $this->t('Cancel'),
+ '#name' => 'Cancel',
+ '#limit_validation_errors' => [],
+ ],
+ ];
+
+ return $form;
+ }
+
+ /**
+ * Builds the form fields for GDPR settings.
+ *
+ * This is in a separate method so it can also be attached to the regular
+ * field settings page by hook.
+ *
+ * @see gdpr_fields_form_field_config_edit_form_submit
+ *
+ * @param array $form
+ * Form
+ * @param string $entity_type
+ * Entity type
+ * @param string $bundle_name
+ * Bundle
+ * @param string $field_name
+ * Field
+ */
+ public static function buildFormFields(array &$form, $entity_type = NULL, $bundle_name = NULL, $field_name = NULL) {
+ $config = static::getConfig($entity_type, $bundle_name, $field_name);
+
+ /* @var \Drupal\gdpr_dump\Sanitizer\GdprSanitizerFactory $sanitizer_factory */
+ $sanitizer_factory = \Drupal::service('gdpr_dump.sanitizer_factory');
+ $sanitizer_definitions = $sanitizer_factory->getDefinitions();
+
+ $form['gdpr_enabled'] = [
+ '#type' => 'checkbox',
+ '#title' => t('This is a GDPR field'),
+ '#default_value' => $config->enabled,
+ ];
+
+ $form['gdpr_rta'] = [
+ '#type' => 'select',
+ '#title' => t('Right to access'),
+ '#options' => [
+ 'inc' => 'Included',
+ 'maybe' => 'Maybe',
+ 'no' => 'Not Included',
+ ],
+ '#default_value' => $config->rta,
+ '#states' => [
+ 'visible' => [
+ ':input[name="gdpr_enabled"]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
+ $form['gdpr_rtf'] = [
+ '#type' => 'select',
+ '#title' => t('Right to be forgotten'),
+ '#options' => [
+ 'anonymise' => 'Anonymise',
+ 'remove' => 'Remove',
+ 'maybe' => 'Maybe',
+ 'no' => 'Not Included',
+ ],
+ '#default_value' => $config->rtf,
+ '#states' => [
+ 'visible' => [
+ ':input[name="gdpr_enabled"]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
+ $sanitizer_options = ['' => ''] + array_map(function ($s) {
+ return $s['label'];
+ }, $sanitizer_definitions);
+
+ $form['gdpr_sanitizer'] = [
+ '#type' => 'select',
+ '#title' => t('Sanitizer to use'),
+ '#options' => $sanitizer_options,
+ '#default_value' => $config->sanitizer,
+ '#states' => [
+ 'visible' => [
+ ':input[name="gdpr_enabled"]' => ['checked' => TRUE],
+ ':input[name="gdpr_rtf"]' => ['value' => 'anonymise'],
+ ],
+ ],
+ ];
+
+ $form['gdpr_notes'] = [
+ '#type' => 'textarea',
+ '#title' => 'Notes',
+ '#default_value' => $config->notes,
+ '#states' => [
+ 'visible' => [
+ ':input[name="gdpr_enabled"]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ if ($form_state->getTriggeringElement()['#name'] == 'Cancel') {
+ $form_state->setRedirect('gdpr_fields.fields_list');
+ return;
+ }
+
+ $config = static::setConfig(
+ $form_state->getValue('entity_type'),
+ $form_state->getValue('bundle'),
+ $form_state->getValue('field_name'),
+ $form_state->getValue('gdpr_enabled'),
+ $form_state->getValue('gdpr_rta'),
+ $form_state->getValue('gdpr_rtf'),
+ $form_state->getValue('gdpr_sanitizer'),
+ $form_state->getValue('gdpr_notes')
+ );
+
+ $config->save();
+ \Drupal::messenger()->addMessage('Field settings saved.');
+ $form_state->setRedirect('gdpr_fields.fields_list');
+ }
+
+}
\ No newline at end of file
diff --git a/modules/gdpr_fields/src/GDPRCollector.php b/modules/gdpr_fields/src/GDPRCollector.php
index 256ec95..881159a 100644
--- a/modules/gdpr_fields/src/GDPRCollector.php
+++ b/modules/gdpr_fields/src/GDPRCollector.php
@@ -10,6 +10,7 @@
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\Core\Url;
use Drupal\ctools\Plugin\RelationshipManager;
+use Drupal\gdpr_fields\Entity\GdprFieldConfigEntity;
/**
* Defines a helper class for stuff related to views data.
@@ -135,9 +136,14 @@ public function getValueEntities(array &$entity_list, $entity_type, EntityInterf
$entity_list[$entity_type][$entity->id()] = $entity;
// Find relationships.
- $context = new Context(new ContextDefinition("entity:{$entity_type}"));
- $definitions = $this->relationshipManager->getDefinitionsForContexts([$context]);
+ $context_definition = new ContextDefinition("entity:{$entity_type}");
+ // @todo Error handling for broken bundles. (Eg. file module).
+ if ($entity->bundle() != 'undefined') {
+ $context_definition->addConstraint('Bundle', [$entity->bundle()]);
+ }
+ $context = new Context($context_definition);
+ $definitions = $this->relationshipManager->getDefinitionsForContexts([$context]);
foreach ($definitions as $definition_id => $definition) {
list($type, $definition_entity, $related_entity_type,) = explode(':', $definition_id);
@@ -149,7 +155,7 @@ public function getValueEntities(array &$entity_list, $entity_type, EntityInterf
// Ignore links back to gdpr_task.
// @todo Remove this once we have solved how to deal with ignored/excluded relationships
- if ($related_entity_type == 'gdpr_task') {
+ if ($related_entity_type == 'gdpr_task' || $related_entity_type == 'message') {
continue;
}
@@ -192,10 +198,11 @@ public function getValueEntities(array &$entity_list, $entity_type, EntityInterf
* @return array
* GDPR entity field list.
*/
- public function listFields($entity_type = 'user', $bundle_id) {
+ public function listFields($entity_type = 'user', $bundle_id, $include_not_configured) {
$storage = $this->entityTypeManager->getStorage($entity_type);
$entity_definition = $this->entityTypeManager->getDefinition($entity_type);
$bundle_type = $entity_definition->getBundleEntityType();
+ $gdpr_settings = GdprFieldConfigEntity::load($entity_type);
// Create a blank entity.
$values = [];
@@ -207,13 +214,20 @@ public function listFields($entity_type = 'user', $bundle_id) {
// Get fields for entity.
$fields = [];
+
+ if (!$include_not_configured && $gdpr_settings == NULL) {
+ return $fields;
+ }
+
foreach ($entity as $field_id => $field) {
/** @var \Drupal\Core\Field\FieldItemListInterface $field */
$field_definition = $field->getFieldDefinition();
$key = "$entity_type.$bundle_id.$field_id";
- $route_name = "entity.field_config.{$entity_type}_field_edit_form";
+ $route_name = 'gdpr_fields.edit_field';
$route_params = [
- 'field_config' => $key,
+ 'entity_type' => $entity_type,
+ 'bundle_name' => $bundle_id,
+ 'field_name' => $field_id,
];
if (isset($bundle_key)) {
@@ -223,8 +237,9 @@ public function listFields($entity_type = 'user', $bundle_id) {
$fields[$key] = [
'title' => $field_definition->getLabel(),
'type' => $field_definition->getType(),
- 'gdpr_rta' => 'None',
- 'gdpr_rtf' => 'None',
+ 'gdpr_rta' => 'Not Configured',
+ 'gdpr_rtf' => 'Not Configured',
+ 'notes' => '',
'edit' => '',
];
@@ -235,11 +250,17 @@ public function listFields($entity_type = 'user', $bundle_id) {
$fields[$key]['edit'] = Link::fromTextAndUrl('edit', $url);
}
}
- $config = $field_definition->getConfig($bundle_id);
- if ($config->getThirdPartySetting('gdpr_fields', 'gdpr_fields_enabled', FALSE)) {
- $fields[$key]['gdpr_rta'] = $config->getThirdPartySetting('gdpr_fields', 'gdpr_fields_rta', 'no');
- $fields[$key]['gdpr_rtf'] = $config->getThirdPartySetting('gdpr_fields', 'gdpr_fields_rtf', 'no');
+ if ($gdpr_settings != NULL) {
+ $field_settings = $gdpr_settings->getField($bundle_id, $field_id);
+ if ($field_settings->configured) {
+ $fields[$key]['gdpr_rta'] = $field_settings->rtaDescription();
+ $fields[$key]['gdpr_rtf'] = $field_settings->rtfDescription();
+ $fields[$key]['notes'] = $field_settings->notes;
+ }
+ elseif (!$field_settings->configured && !$include_not_configured) {
+ unset($fields[$key]);
+ }
}
}
@@ -272,19 +293,26 @@ public function fieldValues($entity_type = 'user', EntityInterface $entity, $ext
$bundle_label = $entity->getEntityType()->getLabel();
}
-
// Get fields for entity.
$fields = [];
+
+ $gdpr_config = GdprFieldConfigEntity::load($entity_type);
+
+ if ($gdpr_config == NULL) {
+ // No fields have been configured on this entity for GDPR.
+ return $fields;
+ }
+
foreach ($entity as $field_id => $field) {
/** @var \Drupal\Core\Field\FieldItemListInterface $field */
$field_definition = $field->getFieldDefinition();
- $config = $field_definition->getConfig($bundle_id);
- if (!$config->getThirdPartySetting('gdpr_fields', 'gdpr_fields_enabled', FALSE)) {
+ $field_config = $gdpr_config->getField($bundle_id, $field->getName());
+
+ if (!$field_config->enabled) {
continue;
}
-
$key = "$entity_type.{$entity->id()}.$field_id";
$fieldValue = $field->getString();
@@ -293,6 +321,7 @@ public function fieldValues($entity_type = 'user', EntityInterface $entity, $ext
'value' => $fieldValue,
'entity' => $entity->getEntityType()->getLabel(),
'bundle' => $bundle_label,
+ 'notes' => $field_config->notes,
];
if (empty($extra_fields)) {
@@ -301,20 +330,23 @@ public function fieldValues($entity_type = 'user', EntityInterface $entity, $ext
// Fetch and validate based on field settings.
if (isset($extra_fields['rta'])) {
- $rta_value = $config->getThirdPartySetting('gdpr_fields', 'gdpr_fields_rta', FALSE);
+ $rta_value = $field_config->rta;
if ($rta_value && $rta_value !== 'no') {
$fields[$key]['gdpr_rta'] = $rta_value;
+ //$fields[$key]['gdpr_rta_desc'] = $field_config->rtaDescription();
}
else {
unset($fields[$key]);
}
}
if (isset($extra_fields['rtf'])) {
- $rtf_value = $config->getThirdPartySetting('gdpr_fields', 'gdpr_fields_rtf', FALSE);
+ $rtf_value = $field_config->rtf;
if ($rtf_value && $rtf_value !== 'no') {
$fields[$key]['gdpr_rtf'] = $rtf_value;
+ //$fields[$key]['gdpr_rtf_desc'] = $field_config->rtfDescription();
+
// For 'maybes', provide a link to edit the entity.
if ($rtf_value == 'maybe') {
$fields[$key]['link'] = $entity->toLink('Edit', 'edit-form');
diff --git a/modules/gdpr_tasks/config/install/core.entity_view_display.gdpr_task.gdpr_remove.default.yml b/modules/gdpr_tasks/config/install/core.entity_view_display.gdpr_task.gdpr_remove.default.yml
index 878976a..5c1e341 100644
--- a/modules/gdpr_tasks/config/install/core.entity_view_display.gdpr_task.gdpr_remove.default.yml
+++ b/modules/gdpr_tasks/config/install/core.entity_view_display.gdpr_task.gdpr_remove.default.yml
@@ -1,3 +1,4 @@
+uuid: 5b759d60-61ce-4d87-82f4-6b81433c1ca3
langcode: en
status: true
dependencies:
@@ -8,23 +9,39 @@ dependencies:
- gdpr_tasks
- options
- user
+_core:
+ default_config_hash: _19d0Pj6VIDv7zxT310E2eIpMDepNswpF3BBp07sH6k
id: gdpr_task.gdpr_remove.default
targetEntityType: gdpr_task
bundle: gdpr_remove
mode: default
content:
+ notes:
+ type: basic_string
+ region: content
+ weight: 3
+ label: inline
+ settings: { }
+ third_party_settings: { }
processed_by:
label: inline
type: author
- weight: 2
+ weight: 4
region: content
settings: { }
third_party_settings: { }
removal_log:
type: gdpr_task_item
- weight: 3
+ weight: 5
+ region: content
+ label: inline
+ settings: { }
+ third_party_settings: { }
+ requested_by:
+ label: inline
+ type: author
+ weight: 2
region: content
- label: above
settings: { }
third_party_settings: { }
status:
diff --git a/modules/gdpr_tasks/config/install/core.entity_view_display.gdpr_task.gdpr_sar.default.yml b/modules/gdpr_tasks/config/install/core.entity_view_display.gdpr_task.gdpr_sar.default.yml
index 77dec62..c013d1d 100644
--- a/modules/gdpr_tasks/config/install/core.entity_view_display.gdpr_task.gdpr_sar.default.yml
+++ b/modules/gdpr_tasks/config/install/core.entity_view_display.gdpr_task.gdpr_sar.default.yml
@@ -1,3 +1,4 @@
+uuid: 5a0777c8-0adc-4245-b1d2-9361cfea7062
langcode: en
status: true
dependencies:
@@ -9,11 +10,34 @@ dependencies:
- file
- options
- user
+_core:
+ default_config_hash: Iqo94n1T957Bb8Qw1BPLcM679fLMl83eGGGZsiqEvOg
id: gdpr_task.gdpr_sar.default
targetEntityType: gdpr_task
bundle: gdpr_sar
mode: default
content:
+ notes:
+ type: basic_string
+ region: content
+ weight: 7
+ label: inline
+ settings: { }
+ third_party_settings: { }
+ processed_by:
+ type: author
+ weight: 6
+ region: content
+ label: inline
+ settings: { }
+ third_party_settings: { }
+ requested_by:
+ label: inline
+ type: author
+ weight: 5
+ region: content
+ settings: { }
+ third_party_settings: { }
sar_export:
type: file_default
weight: 2
@@ -36,12 +60,5 @@ content:
region: content
settings: { }
third_party_settings: { }
- processed_by:
- type: author
- weight: 5
- region: content
- label: inline
- settings: { }
- third_party_settings: { }
hidden:
manual_data: true
diff --git a/modules/gdpr_tasks/gdpr_task.page.inc b/modules/gdpr_tasks/gdpr_task.page.inc
index 9328872..b5a012f 100644
--- a/modules/gdpr_tasks/gdpr_task.page.inc
+++ b/modules/gdpr_tasks/gdpr_task.page.inc
@@ -43,6 +43,7 @@ function template_preprocess_gdpr_task(array &$variables) {
t('Type'),
t('Entity'),
t('Bundle'),
+ t('Notes'),
t('Right to be forgotten'),
'',
],
@@ -64,6 +65,7 @@ function template_preprocess_gdpr_task(array &$variables) {
t('Type'),
t('Entity'),
t('Bundle'),
+ t('Notes'),
t('Right to access'),
],
'#rows' => $rows,
diff --git a/modules/gdpr_tasks/gdpr_tasks.info.yml b/modules/gdpr_tasks/gdpr_tasks.info.yml
index bc48549..793fae2 100644
--- a/modules/gdpr_tasks/gdpr_tasks.info.yml
+++ b/modules/gdpr_tasks/gdpr_tasks.info.yml
@@ -6,4 +6,5 @@ package: General Data Protection Regulation
dependencies:
- gdpr:gdpr
- - gdpr:dump
+ - gdpr:gdpr_dump
+ - gdpr:gdpr_fields
diff --git a/modules/gdpr_tasks/gdpr_tasks.links.action.yml b/modules/gdpr_tasks/gdpr_tasks.links.action.yml
index b2e1412..604a742 100644
--- a/modules/gdpr_tasks/gdpr_tasks.links.action.yml
+++ b/modules/gdpr_tasks/gdpr_tasks.links.action.yml
@@ -2,7 +2,6 @@ gdpr_tasks.request_export:
route_name: gdpr_tasks.request
route_parameters:
gdpr_task_type: 'gdpr_sar'
- user: '1'
title: 'Request data export'
appears_on:
- view.gdpr_tasks_my_data_requests.page_1
@@ -11,7 +10,6 @@ gdpr_tasks.request_remove:
route_name: gdpr_tasks.request
route_parameters:
gdpr_task_type: 'gdpr_remove'
- user: '1'
title: 'Request data removal'
appears_on:
- view.gdpr_tasks_my_data_requests.page_1
diff --git a/modules/gdpr_tasks/gdpr_tasks.module b/modules/gdpr_tasks/gdpr_tasks.module
index d81d694..9a6555b 100644
--- a/modules/gdpr_tasks/gdpr_tasks.module
+++ b/modules/gdpr_tasks/gdpr_tasks.module
@@ -158,27 +158,3 @@ function gdpr_tasks_entity_presave(EntityInterface $entity) {
break;
}
}
-
-/**
- * Implements hook_entity_base_field_info_alter().
- *
- * Ensures that anonymize/removal settings are always set for username, email
- * password and roles. These fields are not UI-editable so we set these in code.
- */
-function gdpr_tasks_entity_base_field_info_alter(&$fields, \Drupal\Core\Entity\EntityTypeInterface $entity_type) {
- if ($entity_type->id() == 'user') {
- $fields['mail']['third_party_settings']['gdpr_fields']['gdpr_fields_enabled'] = TRUE;
- $fields['mail']['third_party_settings']['gdpr_fields']['gdpr_fields_rta'] = 'inc';
- $fields['mail']['third_party_settings']['gdpr_fields']['gdpr_fields_rtf'] = 'anonymise';
- $fields['mail']['third_party_settings']['gdpr_fields']['gdpr_fields_sanitizer'] = 'gdpr_email_sanitizer';
-
- $fields['name']['third_party_settings']['gdpr_fields']['gdpr_fields_enabled'] = TRUE;
- $fields['name']['third_party_settings']['gdpr_fields']['gdpr_fields_rta'] = 'inc';
- $fields['name']['third_party_settings']['gdpr_fields']['gdpr_fields_rtf'] = 'anonymise';
- $fields['name']['third_party_settings']['gdpr_fields']['gdpr_fields_sanitizer'] = 'gdpr_random_text_sanitizer';
-
- $fields['roles']['third_party_settings']['gdpr_fields']['gdpr_fields_enabled'] = TRUE;
- $fields['roles']['third_party_settings']['gdpr_fields']['gdpr_fields_rta'] = 'no';
- $fields['roles']['third_party_settings']['gdpr_fields']['gdpr_fields_rtf'] = 'remove';
- }
-}
diff --git a/modules/gdpr_tasks/gdpr_tasks.permissions.yml b/modules/gdpr_tasks/gdpr_tasks.permissions.yml
index 90c6d48..99208a2 100644
--- a/modules/gdpr_tasks/gdpr_tasks.permissions.yml
+++ b/modules/gdpr_tasks/gdpr_tasks.permissions.yml
@@ -1,11 +1,16 @@
+view gdpr data summary:
+ title: 'Access GDPR Data Summary'
+ description: 'Allows users to access their data without staff involvement'
+
create gdpr tasks:
title: 'Create GDPR tasks'
view gdpr tasks:
title: 'View GDPR tasks'
+ description: 'Allow a user to view completed tasks that they requested'
-edit gdpr tasks:
- title: 'Edit GDPR tasks'
+#edit gdpr tasks:
+# title: 'Edit GDPR tasks'
add task entities:
title: 'Create new Task entities'
@@ -16,9 +21,12 @@ administer task entities:
delete task entities:
title: 'Delete Task entities'
+ description: 'Allows staff to delete GDPR tasks'
edit task entities:
title: 'Edit Task entities'
+ description: 'Allows staff to edit GDPR tasks'
view task entities:
title: 'View Task entities'
+ description: 'Allows staff to view the list of GDPR tasks'
\ No newline at end of file
diff --git a/modules/gdpr_tasks/gdpr_tasks.routing.yml b/modules/gdpr_tasks/gdpr_tasks.routing.yml
index 2d244f0..35bd197 100644
--- a/modules/gdpr_tasks/gdpr_tasks.routing.yml
+++ b/modules/gdpr_tasks/gdpr_tasks.routing.yml
@@ -10,7 +10,7 @@ gdpr_tasks.request:
path: '/user/{user}/gdpr-request/{gdpr_task_type}'
defaults:
_controller: '\Drupal\gdpr_tasks\Controller\GDPRController::requestPage'
- _title: 'GDPR Summary'
+ _title: 'Create Data Request'
requirements:
_permission: 'create gdpr tasks'
options:
diff --git a/modules/gdpr_tasks/gdpr_tasks.rules.events.yml b/modules/gdpr_tasks/gdpr_tasks.rules.events.yml
new file mode 100644
index 0000000..dc784b3
--- /dev/null
+++ b/modules/gdpr_tasks/gdpr_tasks.rules.events.yml
@@ -0,0 +1,18 @@
+gdpr_tasks.rules_rta_complete:
+ label: 'Data access request completed'
+ category: 'GDPR'
+ context:
+ user:
+ type: 'entity:user'
+ label: 'The user'
+ link:
+ type: 'string'
+ label: 'The URL to download the report'
+
+gdpr_tasks.rules_rtf_complete:
+ label: 'Right to be forgotten completed'
+ category: 'GDPR'
+ context:
+ email:
+ type: 'string'
+ label: 'The email address of the recipient'
\ No newline at end of file
diff --git a/modules/gdpr_tasks/src/Anonymizer.php b/modules/gdpr_tasks/src/Anonymizer.php
index bdd3cfa..96795fe 100644
--- a/modules/gdpr_tasks/src/Anonymizer.php
+++ b/modules/gdpr_tasks/src/Anonymizer.php
@@ -11,6 +11,7 @@
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\TypedData\Exception\ReadOnlyException;
use Drupal\gdpr_dump\Sanitizer\GdprSanitizerFactory;
+use Drupal\gdpr_fields\Entity\GdprFieldConfigEntity;
use Drupal\gdpr_fields\GDPRCollector;
use Drupal\gdpr_tasks\Entity\TaskInterface;
use Drupal\gdpr_tasks\Form\RemovalSettingsForm;
@@ -253,11 +254,12 @@ private function anonymize(FieldItemListInterface $field, EntityInterface $bundl
*/
private function getSanitizerId(FieldItemListInterface $field, EntityInterface $bundle_entity) {
// First check if this field has a sanitizer defined.
+
+ $config = GdprFieldConfigEntity::load($bundle_entity->getEntityTypeId());
+ $field_config = $config->getField($bundle_entity->bundle(), $field->getName());
+ $sanitizer = $field_config->sanitizer;
$fieldDefinition = $field->getFieldDefinition();
$type = $fieldDefinition->getType();
- $sanitizer = $fieldDefinition
- ->getConfig($bundle_entity->bundle())
- ->getThirdPartySetting('gdpr_fields', 'gdpr_fields_sanitizer');
if (!$sanitizer) {
// No sanitizer defined directly on the field.
@@ -285,20 +287,19 @@ private function getSanitizerId(FieldItemListInterface $field, EntityInterface $
*/
private function getFieldsToProcess(EntityInterface $entity) {
$bundle_id = $entity->bundle();
+ $config = GdprFieldConfigEntity::load($entity->getEntityTypeId());
// Get fields for entity.
$fields = [];
foreach ($entity as $field_id => $field) {
/** @var \Drupal\Core\Field\FieldItemListInterface $field */
- $field_definition = $field->getFieldDefinition();
-
- $config = $field_definition->getConfig($bundle_id);
+ $field_config = $config->getField($bundle_id, $field->getName());
- if (!$config->getThirdPartySetting('gdpr_fields', 'gdpr_fields_enabled', FALSE)) {
+ if (!$field_config->enabled) {
continue;
}
- $rtf_value = $config->getThirdPartySetting('gdpr_fields', 'gdpr_fields_rtf', FALSE);
+ $rtf_value = $field_config->rtf;
if ($rtf_value && $rtf_value !== 'no') {
$fields[] = [
diff --git a/modules/gdpr_tasks/src/Controller/GDPRController.php b/modules/gdpr_tasks/src/Controller/GDPRController.php
index 18aac7a..b2871bc 100644
--- a/modules/gdpr_tasks/src/Controller/GDPRController.php
+++ b/modules/gdpr_tasks/src/Controller/GDPRController.php
@@ -75,8 +75,7 @@ public function summaryPage() {
* @param $gdpr_task_type
* Type of task to be created.
*
- * @return \Symfony\Component\HttpFoundation\RedirectResponse
- * Return user to GDPR requests.
+ * @return
*/
public function requestPage(AccountInterface $user, $gdpr_task_type) {
$tasks = $this->taskManager->getUserTasks($user, $gdpr_task_type);
@@ -85,6 +84,16 @@ public function requestPage(AccountInterface $user, $gdpr_task_type) {
$this->messenger->addWarning('You already have a pending task.');
}
else {
+ // If the current user is making a request for themselves, just create it.
+ // However, if we're a member of staff making a request on behalf
+ // of someone else, we need to collect further details
+ // so render a form to get the notes.
+ if ($this->currentUser()->id() != $user->id()) {
+ return [
+ 'form' => $this->formBuilder()->getForm('\Drupal\gdpr_tasks\Form\CreateGdprRequestOnBehalfOfUserForm'),
+ ];
+ }
+
$values = [
'type' => $gdpr_task_type,
'user_id' => $user->id(),
@@ -95,9 +104,7 @@ public function requestPage(AccountInterface $user, $gdpr_task_type) {
$this->messenger->addStatus('Your request has been logged');
}
- $response = new RedirectResponse(Url::fromRoute('view.gdpr_tasks_my_data_requests.page_1', ['user' => $user->id()])
- ->toString());
- return $response;
+ return $this->redirect('view.gdpr_tasks_my_data_requests.page_1', ['user' => $user->id()]);
}
}
diff --git a/modules/gdpr_tasks/src/Entity/Task.php b/modules/gdpr_tasks/src/Entity/Task.php
index d554e44..c5f3932 100644
--- a/modules/gdpr_tasks/src/Entity/Task.php
+++ b/modules/gdpr_tasks/src/Entity/Task.php
@@ -63,7 +63,7 @@ class Task extends ContentEntityBase implements TaskInterface {
public static function preCreate(EntityStorageInterface $storage_controller, array &$values) {
parent::preCreate($storage_controller, $values);
$values += [
- 'user_id' => \Drupal::currentUser()->id(),
+ 'requested_by' => \Drupal::currentUser()->id(),
];
}
@@ -169,8 +169,8 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields = parent::baseFieldDefinitions($entity_type);
$fields['user_id'] = BaseFieldDefinition::create('entity_reference')
- ->setLabel(t('Requested by'))
- ->setDescription(t('The user who requested this task.'))
+ ->setLabel(t('User'))
+ ->setDescription(t('The user whose data should be processed.'))
->setRevisionable(TRUE)
->setSetting('target_type', 'user')
->setSetting('handler', 'default')
@@ -240,6 +240,40 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
->setDisplayConfigurable('form', TRUE)
->setDisplayConfigurable('view', TRUE);
+ $fields['requested_by'] = BaseFieldDefinition::create('entity_reference')
+ ->setLabel(t('Requested by'))
+ ->setDescription(t('The user who requested this task.'))
+ ->setRevisionable(TRUE)
+ ->setSetting('target_type', 'user')
+ ->setSetting('handler', 'default')
+ ->setTranslatable(TRUE)
+ ->setDisplayOptions('view', [
+ 'type' => 'author',
+ 'weight' => 6,
+ ])
+ ->setDisplayOptions('form', [
+ 'type' => 'hidden',
+ 'weight' => 6,
+ 'settings' => [
+ 'match_operator' => 'CONTAINS',
+ 'size' => '60',
+ 'autocomplete_type' => 'tags',
+ 'placeholder' => '',
+ ],
+ ])
+ ->setDisplayConfigurable('form', TRUE)
+ ->setDisplayConfigurable('view', TRUE);
+
+ $fields['notes'] = BaseFieldDefinition::create('string_long')
+ ->setLabel(t('Notes'))
+ ->setDescription(t('Notes with this request'))
+ ->setRequired(FALSE)
+ ->setDisplayOptions('view', [
+ 'type' => 'textfield',
+ ])
+ ->setDisplayConfigurable('form', TRUE)
+ ->setDisplayConfigurable('view', TRUE);
+
return $fields;
}
diff --git a/modules/gdpr_tasks/src/Event/RightToAccessCompleteEvent.php b/modules/gdpr_tasks/src/Event/RightToAccessCompleteEvent.php
new file mode 100644
index 0000000..b69ce9d
--- /dev/null
+++ b/modules/gdpr_tasks/src/Event/RightToAccessCompleteEvent.php
@@ -0,0 +1,44 @@
+user = $user;
+ $this->link = $link;
+ }
+
+}
diff --git a/modules/gdpr_tasks/src/Event/RightToBeForgottenCompleteEvent.php b/modules/gdpr_tasks/src/Event/RightToBeForgottenCompleteEvent.php
new file mode 100644
index 0000000..77a3dfe
--- /dev/null
+++ b/modules/gdpr_tasks/src/Event/RightToBeForgottenCompleteEvent.php
@@ -0,0 +1,37 @@
+email = $email;
+ }
+
+}
diff --git a/modules/gdpr_tasks/src/Form/CreateGdprRequestOnBehalfOfUserForm.php b/modules/gdpr_tasks/src/Form/CreateGdprRequestOnBehalfOfUserForm.php
new file mode 100644
index 0000000..d403829
--- /dev/null
+++ b/modules/gdpr_tasks/src/Form/CreateGdprRequestOnBehalfOfUserForm.php
@@ -0,0 +1,58 @@
+ $this->t('Create request on behalf of user'),
+ 'notes' => [
+ '#type' => 'textarea',
+ '#title' => $this->t('Notes'),
+ '#description' => $this->t('Enter the reason for creating this request.'),
+ ],
+ 'actions' => [
+ '#type' => 'actions',
+ 'submit' => [
+ '#type' => 'submit',
+ '#value' => $this->t('Create Request'),
+ ]
+ ]
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ $user_id = $this->getRouteMatch()->getParameter('user');
+ $request_type = $this->getRouteMatch()->getParameter('gdpr_task_type');
+ $notes = $form_state->getValue('notes');
+
+ $task = Task::create([
+ 'type' => $request_type,
+ 'user_id' => $user_id,
+ 'notes' => $notes,
+ ]);
+ $task->save();
+ $this->messenger()->addStatus('The request has been logged');
+ return $this->redirect('view.gdpr_tasks_my_data_requests.page_1', ['user' => $user_id]);
+ }
+}
\ No newline at end of file
diff --git a/modules/gdpr_tasks/src/Form/TaskActionsForm.php b/modules/gdpr_tasks/src/Form/TaskActionsForm.php
index 4ae5795..af918d7 100644
--- a/modules/gdpr_tasks/src/Form/TaskActionsForm.php
+++ b/modules/gdpr_tasks/src/Form/TaskActionsForm.php
@@ -5,6 +5,8 @@
use Drupal\Console\Bootstrap\Drupal;
use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Form\FormStateInterface;
+use Drupal\gdpr_tasks\Event\RightToAccessCompleteEvent;
+use Drupal\gdpr_tasks\Event\RightToBeForgottenCompleteEvent;
use Drupal\user\Entity\User;
/**
@@ -35,6 +37,8 @@ public function buildForm(array $form, FormStateInterface $form_state) {
* Performs the SAR export.
*/
private function doSarExport(FormStateInterface $form_state): void {
+ /* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher */
+ $dispatcher = \Drupal::service('event_dispatcher');
$entity = $this->entity;
$manual = $form_state->getValue(['manual_data', 0, 'value']);
@@ -62,6 +66,8 @@ private function doSarExport(FormStateInterface $form_state): void {
// @todo Add headers to csv export.
file_save_data($export, $file_uri, FILE_EXISTS_REPLACE);
+
+ $dispatcher->dispatch(RightToAccessCompleteEvent::EVENT_NAME, new RightToAccessCompleteEvent($entity->getOwner(), $file_uri));
}
/**
@@ -69,8 +75,18 @@ private function doSarExport(FormStateInterface $form_state): void {
*/
private function doRemoval(FormStateInterface $form_state) {
// @todo Should be injected
+ /* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher */
+ /* @var $entity \Drupal\gdpr_tasks\Entity\Task */
+ $entity = $this->entity;
+ $email = $entity->getOwner()->getEmail();
+ $dispatcher = \Drupal::service('event_dispatcher');
$anonymizer = \Drupal::service('gdpr_tasks.anonymizer');
$errors = $anonymizer->run($this->entity);
+
+ if (count($errors) == 0) {
+ $dispatcher->dispatch(RightToBeForgottenCompleteEvent::EVENT_NAME, new RightToBeForgottenCompleteEvent($email));
+ }
+
return $errors;
}
diff --git a/modules/gdpr_tasks/src/TaskListBuilder.php b/modules/gdpr_tasks/src/TaskListBuilder.php
index 61faf32..2a524c0 100644
--- a/modules/gdpr_tasks/src/TaskListBuilder.php
+++ b/modules/gdpr_tasks/src/TaskListBuilder.php
@@ -59,6 +59,7 @@ public function buildHeader() {
$header['user'] = $this->t('User');
$header['type'] = $this->t('Type');
$header['created'] = $this->t('Requested');
+ $header['requested_by'] = $this->t('Requested by');
return $header + parent::buildHeader();
}
@@ -82,6 +83,7 @@ public function buildRow(EntityInterface $entity) {
'granularity' => 1,
]) . ' ago';
+ $row['requested_by'] = $entity->requested_by->entity->toLink()->toString();
return $row + parent::buildRow($entity);
}
@@ -132,7 +134,7 @@ public function render() {
'#type' => 'table',
'#header' => $this->buildHeader(),
'#rows' => [],
- '#empty' => $this->t('There is no closed @label yet.', ['@label' => $this->entityType->getLabel()]),
+ '#empty' => $this->t('There are no closed tasks yet.'),
'#cache' => [
'contexts' => $this->entityType->getListCacheContexts(),
'tags' => $this->entityType->getListCacheTags(),
diff --git a/modules/gdpr_view_export_log/gdpr_view_export_log.info.yml b/modules/gdpr_view_export_log/gdpr_view_export_log.info.yml
new file mode 100644
index 0000000..76b2bdf
--- /dev/null
+++ b/modules/gdpr_view_export_log/gdpr_view_export_log.info.yml
@@ -0,0 +1,10 @@
+name: General Data Protection Regulation (GDPR) - Log View Exports
+description: Logs view exports
+core: 8.x
+type: module
+package: General Data Protection Regulation
+
+dependencies:
+ - gdpr:gdpr
+ - views_data_export:views_data_export
+ - rules:rules
diff --git a/modules/gdpr_view_export_log/gdpr_view_export_log.install b/modules/gdpr_view_export_log/gdpr_view_export_log.install
new file mode 100644
index 0000000..9cf4acf
--- /dev/null
+++ b/modules/gdpr_view_export_log/gdpr_view_export_log.install
@@ -0,0 +1,77 @@
+getEditable('views.settings');
+ $display_extenders = $config->get('display_extenders') ?: [];
+ $display_extenders[] = 'gdpr_view_export_logging';
+ $config->set('display_extenders', $display_extenders);
+ $config->save();
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function gdpr_view_export_log_uninstall() {
+ $config = \Drupal::service('config.factory')->getEditable('views.settings');
+ $display_extenders = $config->get('display_extenders') ?: [];
+ $key = array_search('gdpr_view_export_logging', $display_extenders);
+ if ($key !== FALSE) {
+ unset($display_extenders[$key]);
+ $config->set('display_extenders', $display_extenders);
+ $config->save();
+ }
+}
+
+
+/**
+ * Implements hook_schema().
+ *
+ * User IDs are stored in a custom table rather than an entity field.
+ * This is so that they can be lazily loaded for performance reasons,
+ * as we don't want to be loading thousands of users when looking up a log entry.
+ */
+function gdpr_view_export_log_schema() {
+ $schema['gdpr_view_export_audit_user_ids'] = [
+ 'description' => 'Stores when Merge Processes are happening on an entity.',
+ 'fields' => [
+ 'id' => [
+ 'type' => 'serial',
+ 'description' => 'Unique Identifier.',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ],
+ 'log_id' => [
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'description' => 'The entity id of the corresponding log entry.',
+ ],
+ 'user_id' => [
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'description' => 'The ID of the user.',
+ ],
+ 'name' => [
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'description' => 'Cache of the user\'s name',
+ ],
+ ],
+ 'primary key' => ['id'],
+ ];
+
+ return $schema;
+}
\ No newline at end of file
diff --git a/modules/gdpr_view_export_log/gdpr_view_export_log.module b/modules/gdpr_view_export_log/gdpr_view_export_log.module
new file mode 100644
index 0000000..4985a54
--- /dev/null
+++ b/modules/gdpr_view_export_log/gdpr_view_export_log.module
@@ -0,0 +1,146 @@
+hasPermission('create gdpr export audits')) {
+ $items['gdpr']['tray']['links']['#links']['exports'] = [
+ 'title' => t('Exports'),
+ 'url' => Url::fromRoute('entity.gdpr_view_export_audit.collection'),
+ 'attributes' => [
+ 'title' => t('Exports'),
+ ],
+ 'weight' => 100,
+ ];
+ }
+}
+
+/**
+ * Implements hook_views_post_render().
+ */
+function gdpr_view_export_log_views_post_render(ViewExecutable $view, &$output, CachePluginBase $cache) {
+}
+
+
+/**
+ * Implements hook_views_post_build().
+ */
+function gdpr_view_export_log_views_post_build(ViewExecutable $view) {
+ if (GdprExportLogDisplayExtender::isLoggingEnabled($view)) {
+
+
+ // Logging is enabled for this view.
+ // Instead of just letting it render, redirect to the audit page,
+ // if we haven't been there already.
+ $already_audited = \Drupal::request()
+ ->getSession()
+ ->get('gdpr_audit_id') > 0;
+
+ if (!$already_audited) {
+
+ //@todo: Do not run in preview mode.
+ $session = \Drupal::request()->getSession();
+
+ $session->set('gdpr_export_audit_file', $view->display_handler->options["filename"]);
+ $session->set('gdpr_export_audit_view', $view->id());
+ $session->set('gdpr_export_audit_continue', \Drupal::request()
+ ->getRequestUri());
+
+ $url = Url::fromRoute('entity.gdpr_view_export_audit.add_form')
+ ->toString();
+ $response = new RedirectResponse($url);
+ $response->send();
+ }
+ }
+
+}
+
+/**
+ * Implements hook_views_post_execute().
+ */
+function gdpr_view_export_log_views_post_execute(ViewExecutable $view) {
+ // After the view is executed, if there has been an audit entry recorded,
+ // Modify the audit to store any user IDs included in the output.
+ if (GdprExportLogDisplayExtender::isLoggingEnabled($view)) {
+ $session = \Drupal::request()->getSession();
+ $audit_entry_id = $session->get('gdpr_audit_id');
+
+ $value_accessors = [];
+
+ if ($audit_entry_id > 0) {
+ $audit_entry = ExportAudit::load($audit_entry_id);
+
+ // fieldDefinition is unfortunately protected.
+ // Use reflection to get it for now
+ // @todo look at not using reflection in future
+ $r = new ReflectionMethod('Drupal\views\Plugin\views\field\EntityField', 'getFieldDefinition');
+ $r->setAccessible(TRUE);
+
+ // Are any of the fields defined against user?
+ foreach ($view->field as $field_id => $field) {
+ // Only process entity fields.
+ if ($field->definition['class'] == 'Drupal\views\Plugin\views\field\EntityField') {
+
+ // If the field is directly defined on the user, log the ID.
+ if ($field->definition['entity_type'] == 'user') {
+ $value_accessors[] = function ($row) use ($field) {
+ return $field->getEntity($row)->id();
+ };
+ }
+
+ $field_definition = $r->invoke($field);
+
+ // If the field is a reference to the user, log the id.
+ if ($field_definition->getType() == 'entity_reference' && $field_definition->getSetting('target_type') == 'user') {
+ $value_accessors[] = function ($row) use ($field) {
+ return $field->getValue($row);
+ };
+ }
+ }
+ }
+
+ $ids = [];
+
+ foreach ($view->result as $row) {
+ foreach ($value_accessors as $accessor) {
+ $id = $accessor($row);
+ if (!in_array($id, $ids)) {
+ $ids[] = $id;
+ }
+ }
+ }
+
+ if (count($ids) > 0) {
+ $audit_entry->save();
+ // User IDs are stored in a custom table rather than an entity field.
+ // This is so that they can be lazily loaded for performance reasons,
+ // as we don't want to be loading thousands of users
+ // when looking up a log entry.
+
+ foreach($ids as $id) {
+ //@todo cache the user's name in here too?
+ \Drupal::database()->insert('gdpr_view_export_audit_user_ids')
+ ->fields([
+ 'log_id' => $audit_entry->id(),
+ 'user_id' => $id,
+ ])->execute();
+ }
+ }
+ }
+
+ $session->remove('gdpr_audit_id');
+
+ }
+
+}
+
diff --git a/modules/gdpr_view_export_log/gdpr_view_export_log.permissions.yml b/modules/gdpr_view_export_log/gdpr_view_export_log.permissions.yml
new file mode 100644
index 0000000..a1a717a
--- /dev/null
+++ b/modules/gdpr_view_export_log/gdpr_view_export_log.permissions.yml
@@ -0,0 +1,2 @@
+create gdpr export audits:
+ title: 'Create audit entries for exporting views'
diff --git a/modules/gdpr_view_export_log/gdpr_view_export_log.routing.yml b/modules/gdpr_view_export_log/gdpr_view_export_log.routing.yml
new file mode 100644
index 0000000..9377f84
--- /dev/null
+++ b/modules/gdpr_view_export_log/gdpr_view_export_log.routing.yml
@@ -0,0 +1,21 @@
+gdpr_view_export_log.view_users:
+ path: '/admin/gdpr/export/{id}/users'
+ defaults:
+ _controller: '\Drupal\gdpr_view_export_log\Controller\ExportAuditController::viewUsers'
+ requirements:
+ _permission: 'create gdpr export audits'
+
+gdpr_view_export_log.delete_user:
+ path: '/admin/gdpr/export/{id}/users/{user_id}/remove'
+ defaults:
+ _form: '\Drupal\gdpr_view_export_log\Form\RemoveUserFromExportForm'
+ requirements:
+ _permission: 'create gdpr export audits'
+
+gdpr_view_export_log.exports_containing_user:
+ path: '/admin/gdpr/exports_containing_user/{user_id}'
+ defaults:
+ #_controller: '\Drupal\gdpr_view_export_log\Controller\ExportAuditController::viewExports'
+ _entity_list: 'gdpr_view_export_audit'
+ requirements:
+ _permission: 'create gdpr export audits'
\ No newline at end of file
diff --git a/modules/gdpr_view_export_log/src/Controller/ExportAuditController.php b/modules/gdpr_view_export_log/src/Controller/ExportAuditController.php
new file mode 100644
index 0000000..164b7a0
--- /dev/null
+++ b/modules/gdpr_view_export_log/src/Controller/ExportAuditController.php
@@ -0,0 +1,99 @@
+select('gdpr_view_export_audit_user_ids', 'g');
+
+ $count_query = clone $query;
+ $count_query->addExpression('count(g.id)');
+
+ $paged_query = $query->extend('Drupal\Core\Database\Query\PagerSelectExtender');
+ $paged_query->limit(50);
+ $paged_query->setCountQuery($count_query);
+ $paged_query->addJoin('left', 'users_field_data', 'u', 'g.user_id = u.uid');
+
+ $results = $paged_query->fields('g', ['user_id'])
+ ->fields('u', ['name'])
+ ->condition('g.log_id', $id)
+ ->execute()
+ ->fetchAll(\PDO::FETCH_ASSOC);
+
+
+ $output = [
+ 'back' => [
+ '#type' => 'link',
+ '#url' => Url::fromRoute('entity.gdpr_view_export_audit.collection'),
+ '#title' => $this->t('Back to export list'),
+ ],
+
+ 'table' => [
+ '#type' => 'table',
+ '#header' => ['ID', 'Username', ''],
+ '#empty' => 'Could not locate any users in this export.',
+ ],
+
+ 'pager' => [
+ '#theme' => 'pager',
+ '#weight' => 5,
+ '#element' => 0,
+ '#parameters' => [],
+ '#quantity' => 9,
+ '#route_name' => '',
+ '#tags' => '',
+ ],
+ ];
+
+
+ foreach ($results as $result) {
+ $output['table'][$result['user_id']]['userid'] = [
+ '#markup' => $result['user_id'],
+ ];
+
+ $output['table'][$result['user_id']]['username'] = [
+ '#type' => 'link',
+ '#url' => Url::fromRoute('entity.user.canonical', ['user' => $result['user_id']]),
+ '#title' => $result['name'],
+ ];
+
+
+ $output['table'][$result['user_id']]['operations'] = [
+ '#type' => 'operations',
+ '#links' => [
+ 'remove' => [
+ 'title' => $this->t('Remove'),
+ 'url' => Url::fromRoute('gdpr_view_export_log.delete_user', [
+ 'id' => $id,
+ 'user_id' => $result['user_id'],
+ ]),
+ ],
+ ],
+ ];
+ }
+
+ return $output;
+ }
+
+}
diff --git a/modules/gdpr_view_export_log/src/Entity/ExportAudit.php b/modules/gdpr_view_export_log/src/Entity/ExportAudit.php
new file mode 100644
index 0000000..0ca6f06
--- /dev/null
+++ b/modules/gdpr_view_export_log/src/Entity/ExportAudit.php
@@ -0,0 +1,129 @@
+setLabel(t('Location'))
+ ->setDescription(t('Where will this export be stored? (For example, a user\'s PC)'))
+ ->setRequired(TRUE)
+ ->setDisplayOptions('form', [
+ 'type' => 'textfield',
+ 'weight' => 1,
+ ])
+ ->setDisplayConfigurable('form', TRUE)
+ ->setDisplayConfigurable('view', TRUE);
+
+
+ $fields['reason'] = BaseFieldDefinition::create('string_long')
+ ->setLabel(t('Reason'))
+ ->setDescription(t('The reason for the export'))
+ ->setRequired(TRUE)
+ ->setDisplayOptions('form', [
+ 'type' => 'textfield_long',
+ 'weight' => 2,
+ ])
+ ->setDisplayConfigurable('form', TRUE)
+ ->setDisplayConfigurable('view', TRUE);
+
+ $fields['length'] = BaseFieldDefinition::create('integer')
+ ->setLabel(t('Length'))
+ ->setDescription(t('Length (in a days) that the export should live for.'))
+ ->setDefaultValue(90)
+ ->setRequired(TRUE)
+ ->setDisplayOptions('form', [
+ 'type' => 'number',
+ 'weight' => 3,
+ ])
+ ->setDisplayConfigurable('form', TRUE)
+ ->setDisplayConfigurable('view', TRUE);
+
+
+
+ $fields['owner'] = BaseFieldDefinition::create('entity_reference')
+ ->setLabel(t('Authored by'))
+ ->setDescription(t('The user ID of author of the audit entry.'))
+ ->setRevisionable(TRUE)
+ ->setSetting('target_type', 'user')
+ ->setSetting('handler', 'default')
+ ->setTranslatable(TRUE)
+ ->setDisplayOptions('view', [
+ // 'label' => 'hidden',
+ 'type' => 'author',
+ ])
+ ->setDisplayOptions('form', [
+ 'type' => 'hidden',
+ 'settings' => [
+ 'match_operator' => 'CONTAINS',
+ 'size' => '60',
+ 'autocomplete_type' => 'tags',
+ 'placeholder' => '',
+ ],
+ ])
+ ->setDisplayConfigurable('form', TRUE)
+ ->setDisplayConfigurable('view', TRUE);
+
+ $fields['created'] = BaseFieldDefinition::create('created')
+ ->setLabel(t('Created'))
+ ->setDescription(t('The time that the entity was created.'));
+
+ $fields['filename'] = BaseFieldDefinition::create('string')
+ ->setLabel(t('File name'))
+ ->setDescription(t('The name of the file'));
+
+ $fields['view'] = BaseFieldDefinition::create('string')
+ ->setLabel(t('View'))
+ ->setDescription(t('The name of the view'));
+
+ return $fields;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/gdpr_view_export_log/src/Entity/ExportAuditListBuilder.php b/modules/gdpr_view_export_log/src/Entity/ExportAuditListBuilder.php
new file mode 100644
index 0000000..6c40b41
--- /dev/null
+++ b/modules/gdpr_view_export_log/src/Entity/ExportAuditListBuilder.php
@@ -0,0 +1,99 @@
+ 'File Name',
+ 'location' => 'Location',
+ 'reason' => 'Reason',
+ 'created' => 'Date Exported',
+ 'expires' => 'Expires In',
+ 'owner' => 'Exported By',
+ ] + parent::buildHeader();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getEntityIds() {
+ // If a user ID was passed in the route then only load the exports
+ // that contain the specified user ID.
+ $user_id = \Drupal::routeMatch()->getParameter('user_id');
+ if ($user_id) {
+ $query = $this->getStorage()->getQuery()
+ ->condition('user_ids', $user_id)
+ ->sort($this->entityType->getKey('id'));
+
+ // Only add the pager if a limit is specified.
+ if ($this->limit) {
+ $query->pager($this->limit);
+ }
+ return $query->execute();
+
+
+ }
+ else {
+ return parent::getEntityIds();
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildRow(EntityInterface $entity) {
+ $expires = $entity->get('length')->value;
+
+ $date_exported = new \DateTimeImmutable();
+ $date_exported = $date_exported->setTimestamp($entity->get('created')->value);
+ $date_expires = $date_exported->modify("+{$expires} day");
+
+ $now = new \DateTimeImmutable('now');
+ $diff = $now->diff($date_expires);
+
+ $row = [
+ 'filename' => $entity->get('filename')->value,
+ 'location' => $entity->get('location')->value,
+ 'reason' => $entity->get('reason')->value,
+ 'created' => date('Y-m-d H:i:s', $entity->get('created')->value),
+ ];
+
+ $row['expires']['data'] = [
+ '#markup' => $diff->invert ? "{$diff->format('%r%a days')}" : $diff->format('%r%a days'),
+ ];
+
+ $row['user']['data'] = [
+ '#theme' => 'username',
+ '#account' => $entity->get('owner')->entity,
+ ];
+ return $row + parent::buildRow($entity);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildOperations(EntityInterface $entity) {
+ $ops = parent::buildOperations($entity);
+ $ops['#links']['view_users'] = [
+ 'title' => $this->t('View Users'),
+ 'url' => Url::fromRoute('gdpr_view_export_log.view_users', ['id' => $entity->id()]),
+ 'weight' => 1,
+ ];
+ return $ops;
+ }
+
+}
diff --git a/modules/gdpr_view_export_log/src/Form/AuditDeleteForm.php b/modules/gdpr_view_export_log/src/Form/AuditDeleteForm.php
new file mode 100644
index 0000000..6b3548d
--- /dev/null
+++ b/modules/gdpr_view_export_log/src/Form/AuditDeleteForm.php
@@ -0,0 +1,26 @@
+t('Please only remove this export log if you have completely destroyed the export on the target computer and any copies of it. Are you sure you want to continue?');
+ return $form;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/gdpr_view_export_log/src/Form/AuditForm.php b/modules/gdpr_view_export_log/src/Form/AuditForm.php
new file mode 100644
index 0000000..1370c98
--- /dev/null
+++ b/modules/gdpr_view_export_log/src/Form/AuditForm.php
@@ -0,0 +1,74 @@
+t('Please provide some information about your export.');
+
+ $form['intro'] = [
+ '#markup' => 'To continue, please enter details about how this export will be used.',
+ ];
+
+ $session = $this->getRequest()->getSession();
+
+ $form['continue_url'] = [
+ '#type' => 'hidden',
+ '#default_value' => $session->get('gdpr_export_audit_continue'),
+ ];
+
+ $form['filename'] = [
+ '#type' => 'hidden',
+ '#default_value' => $session->get('gdpr_export_audit_file'),
+ ];
+
+ $form['view'] = [
+ '#type' => 'hidden',
+ '#default_value' => $session->get('gdpr_export_audit_view'),
+ ];
+
+ $session->remove('gdpr_export_audit_view');
+ $session->remove('gdpr_export_audit_file');
+ $session->remove('gdpr_export_audit_continue');
+
+ $form['actions']['submit']['#value'] = $this->t('Continue');
+ return $form;
+ }
+
+
+ /**
+ * {@inheritdoc}
+ */
+ public function save(array $form, FormStateInterface $form_state) {
+ $entity = $this->entity;
+ $entity->set('owner', \Drupal::currentUser()->id());
+ parent::save($form, $form_state);
+
+ $url = urldecode($form_state->getValue('continue_url'));
+ if (strpos($url, '?') > -1) {
+ $url .= '&audited=1';
+ }
+ else {
+ $url .= '?audited=1';
+ }
+
+ $this->getRequest()->getSession()->set('gdpr_audit_id', $entity->id());
+ $form_state->setRedirectUrl(Url::fromUserInput($url));
+ }
+
+}
diff --git a/modules/gdpr_view_export_log/src/Form/RemoveUserFromExportForm.php b/modules/gdpr_view_export_log/src/Form/RemoveUserFromExportForm.php
new file mode 100644
index 0000000..51b22a7
--- /dev/null
+++ b/modules/gdpr_view_export_log/src/Form/RemoveUserFromExportForm.php
@@ -0,0 +1,75 @@
+ $this->t('Only remove a user from this export if you have actually removed all their information from the export on the target machine and any copies'),
+ ];
+
+ $form['actions'] = [
+ '#type' => 'actions',
+ 'submit' => [
+ '#type' => 'submit',
+ '#value' => 'Remove',
+ ],
+ ];
+
+ $form['id'] = [
+ '#type' => 'hidden',
+ '#default_value' => $id,
+ ];
+
+ $form['user_id'] = [
+ '#type' => 'hidden',
+ '#default_value' => $user_id,
+ ];
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ $id_to_remove = $form_state->getValue('id');
+ $audit_entry = ExportAudit::load($form_state->getValue('id'));
+
+ foreach ($audit_entry->get('user_ids') as $index => $field) {
+ if ($field->value == $id_to_remove) {
+ $index_to_remove = $index;
+ break;
+ }
+ }
+
+ if (isset($index_to_remove)) {
+ $audit_entry->get('user_ids')->removeItem($index_to_remove);
+ $audit_entry->save();
+ }
+
+ \Drupal::messenger()->addMessage($this->t('User removed'));
+ $form_state->setRedirect('gdpr_view_export_log.view_users', ['id' => $audit_entry->id()]);
+ }
+}
\ No newline at end of file
diff --git a/modules/gdpr_view_export_log/src/Plugin/views/display_extender/GdprExportLogDisplayExtender.php b/modules/gdpr_view_export_log/src/Plugin/views/display_extender/GdprExportLogDisplayExtender.php
new file mode 100644
index 0000000..b549723
--- /dev/null
+++ b/modules/gdpr_view_export_log/src/Plugin/views/display_extender/GdprExportLogDisplayExtender.php
@@ -0,0 +1,100 @@
+displayHandler) == ExportAudit::exportDisplayHandler();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function defineOptionsAlter(&$options) {
+ $options['gdpr_log'] = [
+ 'default' => FALSE,
+ 'contains' => [
+ 'log_exports' => ['default' => 0],
+ ],
+ ];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function optionsSummary(&$categories, &$options) {
+ if ($this->isExportView()) {
+ $categories['gdpr_log'] = [
+ 'title' => 'GDPR',
+ 'column' => 'second',
+ ];
+
+ $options['gdpr_log'] = [
+ 'category' => 'gdpr_log',
+ 'title' => 'Log Exports',
+ 'value' => $this->loggingEnabled() ? 'Yes' : 'No',
+ ];
+ }
+ }
+
+ public function loggingEnabled() {
+ return array_key_exists('gdpr_log', $this->options) && $this->options['gdpr_log']['log_exports'] == TRUE;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
+ if ($form_state->get('section') == 'gdpr_log') {
+ $form['#title'] .= 'The GDPR Log settings';
+
+ $form['gdpr_log']['#type'] = 'container';
+ $form['gdpr_log']['#tree'] = TRUE;
+ $form['gdpr_log']['log_exports'] = [
+ '#title' => $this->t('Log Exports'),
+ '#description' => $this->t('Whether to log exports of this view'),
+ '#type' => 'checkbox',
+ '#default_value' => $this->loggingEnabled(),
+ ];
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function submitOptionsForm(&$form, FormStateInterface $form_state) {
+ if ($form_state->get('section') == 'gdpr_log') {
+ //$should_log = $form_state->getValue('gdpr_log')['log_exports'];
+ $this->options['gdpr_log'] = $form_state->getValue('gdpr_log');
+ }
+ }
+
+ public static function isLoggingEnabled(ViewExecutable $view) {
+ if (get_class($view->display_handler) == ExportAudit::exportDisplayHandler()) {
+ $extenders = $view->getDisplay()->getExtenders();
+ if (array_key_exists('gdpr_view_export_logging', $extenders)) {
+ return $extenders['gdpr_view_export_logging']->options['gdpr_log']['log_exports'] == 1;
+ }
+ }
+
+ return FALSE;
+ }
+
+}
\ No newline at end of file
diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php
index 8940724..6b6820d 100644
--- a/src/Controller/UserController.php
+++ b/src/Controller/UserController.php
@@ -22,10 +22,22 @@ class UserController extends ControllerBase {
* @return \Drupal\Core\Access\AccessResultInterface
* The result.
*/
- public function access(UserInterface $user) {
+ public function accessCollectedData(UserInterface $user) {
+ // Must have the appropriate permission to access the page.
+ // This allows site admins to completely disable the View My Data page.
+ if (!$this->currentUser()->hasPermission('view gdpr data summary')) {
+ return AccessResult::forbidden();
+ }
+
+ // If access to the page is enabled,
+ // only show the tab if we're viewing our OWN profile
+ // or we're a GDPR admin.
if ((int) $user->id() === (int) $this->currentUser()->id()) {
return AccessResult::allowed();
}
+ elseif ($this->currentUser()->hasPermission('view task entities')) {
+ return AccessResult::allowed();
+ }
return AccessResult::forbidden();
}