diff --git a/README.md b/README.md
index 7af4fdb..eb7715f 100644
--- a/README.md
+++ b/README.md
@@ -25,19 +25,23 @@ be GDPR compliant YO!
## Configuration
-This module can be configured by editing the `gdpr_dumper.settings.yml` [file](https://github.com/robiningelbrecht/gdpr-dumper/blob/master/config/install/gdpr_dumper.settings.yml).
+This module can be configured by navigating to `admin/config/development/gdpr-dumper`.
+On this page you can configure the sanitization and anonymization
+of every column of every table in your database.
+## Events
+
+The module dispatches one event:
+* `GdprDumperEvents::GDPR_REPLACEMENTS`
+
+This allows developers to alter the replacements through event subscribers on run-time.
[machbarmacher/gdpr-dump](https://github.com/machbarmacher/gdpr-dump) contains more info about
-the **gdpr-expressions** and **gdpr-replacement** options.
+the **gdpr-replacement** options.
The provided yml file expects the same structure as explained in the readme above.
-## Events
+## TODO
-The module dispatches two events:
-* `GdprDumperEvents::GDPR_EXPRESSIONS`
-* `GdprDumperEvents::GDPR_REPLACEMENTS`
-
-This allows developers to alter the expressions and replacements through event subscribers on run-time
+* Provide a way to allow to export the structure of a table without the data.
Happy GDPR'ing!
diff --git a/config/install/gdpr_dumper.settings.yml b/config/install/gdpr_dumper.settings.yml
index 5b0c8b5..db863c7 100644
--- a/config/install/gdpr_dumper.settings.yml
+++ b/config/install/gdpr_dumper.settings.yml
@@ -1,6 +1,4 @@
-gdpr_expressions:
- users_field_data:
- init: 'uid'
+empty_tables: {}
gdpr_replacements:
users_field_data:
name:
@@ -8,15 +6,4 @@ gdpr_replacements:
mail:
formatter: 'email'
pass:
- formatter: 'clear'
-drivers:
- mysql:
- dump_command: 'mysqldump'
- oracle:
- dump_command: 'mysqldump'
- pqsql:
- dump_command: 'pg_dump'
- sqlite:
- dump_command: 'dump'
- sqlsrv:
- dump_command: 'mysqldump'
\ No newline at end of file
+ formatter: 'clear'
\ No newline at end of file
diff --git a/gdpr_dumper.libraries.yml b/gdpr_dumper.libraries.yml
new file mode 100644
index 0000000..c4a4267
--- /dev/null
+++ b/gdpr_dumper.libraries.yml
@@ -0,0 +1,8 @@
+settings-form:
+ version: 1.x
+ js:
+ js/gdpr-dumper.js: {}
+ dependencies:
+ - core/jquery
+ - core/drupal.form
+
diff --git a/gdpr_dumper.links.menu.yml b/gdpr_dumper.links.menu.yml
new file mode 100644
index 0000000..d1ae0d5
--- /dev/null
+++ b/gdpr_dumper.links.menu.yml
@@ -0,0 +1,5 @@
+gdpr_dumper.settings:
+ title: 'GDPR dumper'
+ description: 'Configure GDPR dumper drush command'
+ parent: system.admin_config_development
+ route_name: gdpr_dumper.settings
\ No newline at end of file
diff --git a/gdpr_dumper.permissions.yml b/gdpr_dumper.permissions.yml
new file mode 100644
index 0000000..c2e61ad
--- /dev/null
+++ b/gdpr_dumper.permissions.yml
@@ -0,0 +1,3 @@
+administer gdpr dumper:
+ title: 'Administer gdpr dumper'
+ description: 'Allow to configure the gdpr dumper drush command'
diff --git a/gdpr_dumper.routing.yml b/gdpr_dumper.routing.yml
new file mode 100644
index 0000000..7d90552
--- /dev/null
+++ b/gdpr_dumper.routing.yml
@@ -0,0 +1,7 @@
+gdpr_dumper.settings:
+ path: '/admin/config/development/gdpr-dumper'
+ defaults:
+ _form: '\Drupal\gdpr_dumper\Form\GdprDumperSettingsForm'
+ _title: 'GDPR dumper'
+ requirements:
+ _permission: 'administer gdpr dumper'
diff --git a/gdpr_dumper.services.yml b/gdpr_dumper.services.yml
new file mode 100644
index 0000000..80187b9
--- /dev/null
+++ b/gdpr_dumper.services.yml
@@ -0,0 +1,4 @@
+services:
+ gdpr_dumper.database_manager:
+ class: Drupal\gdpr_dumper\Manager\DatabaseManager
+ arguments: ['@database']
\ No newline at end of file
diff --git a/js/gdpr-dumper.js b/js/gdpr-dumper.js
new file mode 100644
index 0000000..9b78ed8
--- /dev/null
+++ b/js/gdpr-dumper.js
@@ -0,0 +1,13 @@
+(function($, Drupal) {
+
+ Drupal.behaviors.gdprDumperSummary = {
+ attach: function (context, settings) {
+ // Display the action in the vertical tab summary.
+ $(context).find('details[data-table-summary]').drupalSetSummary(function(context) {
+ return Drupal.checkPlain($(context).data('table-summary'));
+ });
+
+ }
+ }
+
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/src/Event/GdprDumperEvents.php b/src/Event/GdprDumperEvents.php
index b8a05c4..fafc9f9 100644
--- a/src/Event/GdprDumperEvents.php
+++ b/src/Event/GdprDumperEvents.php
@@ -7,15 +7,6 @@
*/
final class GdprDumperEvents {
- /**
- * Name of the event fired building the GDPR expressions.
- *
- * @Event
- *
- * @see \Drupal\gdpr_dumper\Event\GdprExpressionsEvent
- */
- const GDPR_EXPRESSIONS = 'gdpr_dumper.expressions';
-
/**
* Name of the event fired building the GDPR replacements.
*
diff --git a/src/Event/GdprExpressionsEvent.php b/src/Event/GdprExpressionsEvent.php
deleted file mode 100644
index 6c0b963..0000000
--- a/src/Event/GdprExpressionsEvent.php
+++ /dev/null
@@ -1,43 +0,0 @@
-expressions = $expressions;
- }
-
- /**
- * @return array
- */
- public function getExpressions() {
- return $this->expressions;
- }
-
- /**
- * @param array $expressions
- * @return $this
- */
- public function setExpressions(array $expressions) {
- $this->expressions = $expressions;
- return $this;
- }
-
-}
diff --git a/src/Form/GdprDumperSettingsForm.php b/src/Form/GdprDumperSettingsForm.php
new file mode 100644
index 0000000..ef7eea7
--- /dev/null
+++ b/src/Form/GdprDumperSettingsForm.php
@@ -0,0 +1,229 @@
+connection = $connection;
+ $this->databaseManager = $database_manager;
+ $this->settings = $this->config('gdpr_dumper.settings');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('config.factory'),
+ $container->get('database'),
+ $container->get('gdpr_dumper.database_manager')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFormId() {
+ return 'gdpr_dumper_settings_form';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getEditableConfigNames() {
+ return ['gdpr_dumper.settings'];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildForm(array $form, FormStateInterface $form_state) {
+ $replacements = $this->settings->get('gdpr_replacements');
+ $empty_tables = $this->settings->get('empty_tables');
+ // Add the empty table options to the replacement array, if they don't exist already.
+ // We need this because if only the "Empty table" option is checked,
+ // the table won't be available in the replacements array.
+ foreach (array_filter($empty_tables) as $empty_table => $value) {
+ if (!isset($replacements[$empty_table])) {
+ $replacements[$empty_table] = [];
+ }
+ }
+
+
+ $database_tables = $this->databaseManager->getTableColumns();
+ $db_schema = $this->connection->schema();
+ $schema_handles_db_comments = \is_callable([$db_schema, 'getComment']);
+
+ $form['intro'] = [
+ '#type' => 'item',
+ '#title' => $this->t('Manage tables and columns that contain sensitive data'),
+ ];
+
+ $tables_to_add = array_diff(array_keys($database_tables), array_keys($replacements));
+
+ $form['table'] = [
+ '#type' => 'select',
+ '#title' => $this->t('Select table'),
+ '#title_display' => 'invisible',
+ '#options' => array_combine($tables_to_add, $tables_to_add),
+ '#empty_value' => '',
+ '#empty_option' => $this->t('- Select -'),
+ ];
+
+ $form['add_table'] = [
+ 'actions' => [
+ '#type' => 'actions',
+ 'submit' => [
+ '#type' => 'submit',
+ '#value' => $this->t('Add table'),
+ '#submit' => [
+ [$this, 'submitAddTable']
+ ],
+ ],
+ ],
+ ];
+
+ $form['advanced'] = [
+ '#type' => 'vertical_tabs',
+ ];
+
+ $form['replacements'] = [
+ '#tree' => TRUE,
+ '#attached' => [
+ 'library' => ['gdpr_dumper/settings-form']
+ ],
+ ];
+
+ foreach ($replacements as $table => $columns) {
+ if (isset($database_tables[$table])) {
+ $table_summary = $schema_handles_db_comments ? $db_schema->getComment($table) : '-';
+ $form['replacements'][$table] = [
+ '#type' => 'details',
+ '#title' => $table,
+ '#group' => 'advanced',
+ '#attributes' => [
+ 'data-table-summary' => $table_summary,
+ ],
+ ];
+
+ $form['replacements'][$table]['columns'] = [
+ '#type' => 'table',
+ '#caption' => $table_summary,
+ '#header' => [
+ ['data' => $this->t('Field')],
+ ['data' => $this->t('Type')],
+ ['data' => $this->t('Description')],
+ ['data' => $this->t('Apply anonymization')],
+ ],
+ ];
+
+ foreach ($database_tables[$table] as $column_name => $column_properties) {
+ $form['replacements'][$table]['columns'][$column_name]['field'] = [
+ '#markup' => '' . $column_properties['COLUMN_NAME'] . '',
+ ];
+ $form['replacements'][$table]['columns'][$column_name]['data_type'] = [
+ '#markup' => '' . $column_properties['DATA_TYPE'] . '',
+ ];
+ $form['replacements'][$table]['columns'][$column_name]['comment'] = [
+ '#markup' => '' . (empty($column_properties['COLUMN_COMMENT']) ? '-' : $column_properties['COLUMN_COMMENT']) . '',
+ ];
+ $form['replacements'][$table]['columns'][$column_name]['anonymization'] = [
+ '#type' => 'select',
+ '#title' => $this->t('Apply anonymization'),
+ '#title_display' => 'invisible',
+ '#options' => GdprDumperEnums::fakerFormatters(),
+ '#empty_value' => '',
+ '#empty_option' => $this->t('- No -'),
+ '#required' => FALSE,
+ '#default_value' => isset($replacements[$table][$column_name]['formatter']) ? $replacements[$table][$column_name]['formatter'] : FALSE,
+ ];
+ }
+
+ $form['replacements'][$table]['empty'] = [
+ '#type' => 'checkbox',
+ '#title' => $this->t('Empty this table'),
+ '#button_type' => 'secondary',
+ '#default_value' => isset($empty_tables[$table]) ? $empty_tables[$table] : FALSE,
+ ];
+ }
+ }
+
+ return parent::buildForm($form, $form_state);
+ }
+
+ /**
+ * Submit callback to add a table to the list.
+ *
+ * @param array $form
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ */
+ public function submitAddTable(array &$form, FormStateInterface $form_state) {
+ if ($table = $form_state->getValue('table')) {
+ $replacements = $this->settings->get('gdpr_replacements');
+
+ if (!isset($replacements[$table])) {
+ $replacements[$table] = [];
+ }
+
+ // Order tables alphabetically before saving.
+ ksort($replacements);
+
+ // Update config.
+ $this->settings->set('gdpr_replacements', $replacements)->save();
+ $this->messenger()
+ ->addStatus($this->t('The table has been added. You can configure it by selecting the corresponding tab.'));
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ $settings = [
+ 'gdpr_replacements' => [],
+ 'empty_tables' => [],
+ ];
+
+ $replacements = $form_state->getValue('replacements');
+ // Format the replacement to a suitable config array.
+ foreach ($replacements as $table_name => $properties) {
+ foreach ($properties['columns'] as $column_name => $column) {
+ if (!empty($column['anonymization'])) {
+ $settings['gdpr_replacements'][$table_name][$column_name]['formatter'] = $column['anonymization'];
+ }
+ };
+ $settings['empty_tables'][$table_name] = $properties['empty'];
+ }
+
+ // Save settings.
+ $this->settings->set('gdpr_replacements', $settings['gdpr_replacements'])
+ ->set('empty_tables', $settings['empty_tables'])->save();
+
+ parent::submitForm($form, $form_state);
+ }
+
+}
\ No newline at end of file
diff --git a/src/GdprDumperEnums.php b/src/GdprDumperEnums.php
new file mode 100644
index 0000000..98068f2
--- /dev/null
+++ b/src/GdprDumperEnums.php
@@ -0,0 +1,57 @@
+ 'Generate a name',
+ 'phoneNumber' => t('Generate a phone number'),
+ 'username' => t('Generate a random user name'),
+ 'password' => t('Generate a random password'),
+ 'email' => t('Generate a random email address'),
+ 'date' => t('Generate a date'),
+ 'longText' => t('Generate a sentence'),
+ 'number' => t('Generate a number'),
+ 'randomText' => t('Generate a sentence'),
+ 'text' => t('Generate a paragraph'),
+ 'uri' => t('Generate a URI'),
+ 'clear' => t('Generate an empty string'),
+ ];
+ }
+
+ /**
+ * @param $driver
+ * @return array
+ */
+ public static function driverOptions($driver) {
+ $map = [
+ 'mysql' => [
+ 'dump_command' => 'mysqldump',
+ ],
+ 'oracle' => [
+ 'dump_command' => 'mysqldump',
+ ],
+ 'pqsql' => [
+ 'dump_command' => 'pg_dump',
+ ],
+ 'sqlite' => [
+ 'dump_command' => 'dump',
+ ],
+ 'sqlsrv' => [
+ 'dump_command' => 'mysqldump',
+ ],
+ ];
+
+ return isset($map[$driver]) ? $map[$driver] : [];
+ }
+
+}
\ No newline at end of file
diff --git a/src/Manager/DatabaseManager.php b/src/Manager/DatabaseManager.php
new file mode 100644
index 0000000..e891612
--- /dev/null
+++ b/src/Manager/DatabaseManager.php
@@ -0,0 +1,53 @@
+database = $database;
+ }
+
+ /**
+ * @return array
+ */
+ public function getTableColumns() {
+ $tables = $this->database->schema()->findTables('%');
+ $columns = [];
+ foreach ($tables as $table) {
+ $result = $this->getColumns($table);
+ if (NULL === $result) {
+ continue;
+ }
+ $columns[$table] = $result->fetchAllAssoc('COLUMN_NAME', \PDO::FETCH_ASSOC);
+ }
+
+ return $columns;
+ }
+
+ /**
+ * @param $table
+ * @return \Drupal\Core\Database\StatementInterface|null
+ */
+ protected function getColumns($table) {
+ // @todo: How cross-driver is this?
+ $query = $this->database->select('information_schema.columns', 'columns');
+ $query->fields('columns', ['COLUMN_NAME', 'DATA_TYPE', 'COLUMN_COMMENT']);
+ $query->condition('TABLE_SCHEMA', $this->database->getConnectionOptions()['database']);
+ $query->condition('TABLE_NAME', $table);
+ return $query->execute();
+ }
+}
\ No newline at end of file
diff --git a/src/Sql/GdprSqlBase.php b/src/Sql/GdprSqlBase.php
index 72c3341..ea1d8b1 100644
--- a/src/Sql/GdprSqlBase.php
+++ b/src/Sql/GdprSqlBase.php
@@ -5,8 +5,8 @@
use Drupal\Component\Serialization\Json;
use Drupal\Core\Database\Database;
use Drupal\gdpr_dumper\Event\GdprDumperEvents;
-use Drupal\gdpr_dumper\Event\GdprExpressionsEvent;
use Drupal\gdpr_dumper\Event\GdprReplacementsEvent;
+use Drupal\gdpr_dumper\GdprDumperEnums;
use Drush\Drush;
use Drush\Sql\SqlBase;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@@ -61,17 +61,6 @@ public static function getInstance($dbSpec, $options, EventDispatcherInterface $
// Fetch module settings.
$config = \Drupal::config('gdpr_dumper.settings');
- if (empty($options['extra-dump']) || strpos($options['extra-dump'], '--gdpr-expressions') === FALSE) {
-
- // Dispatch event so the expressions can be altered.
- $event = new GdprExpressionsEvent($config->get('gdpr_expressions'));
- $event_dispatcher->dispatch(GdprDumperEvents::GDPR_EXPRESSIONS, $event);
- // Add the configured GDPR expressions to the command.
- if($expressions = Json::encode($event->getExpressions())){
- $options['extra-dump'] .= " --gdpr-expressions='$expressions'";
- }
- }
-
if (empty($options['extra-dump']) || strpos($options['extra-dump'], '--gdpr-replacements') === FALSE) {
// Dispatch event so the replacements can be altered.
$event = new GdprReplacementsEvent($config->get('gdpr_replacements'));
@@ -83,10 +72,9 @@ public static function getInstance($dbSpec, $options, EventDispatcherInterface $
}
$instance = new $className($dbSpec, $options);
- $driver_options = isset($config->get('drivers')[$driver]) ? $config->get('drivers')[$driver] : [];
// Inject config
$instance->setConfig(Drush::config());
- $instance->setDriverOptions($driver_options);
+ $instance->setDriverOptions(GdprDumperEnums::driverOptions($driver));
return $instance;
}