From 093fe65b702df3f1083d64c602c70ba3eaea2178 Mon Sep 17 00:00:00 2001 From: Sjoerd van der Vis Date: Tue, 16 Jun 2020 14:58:14 +0200 Subject: [PATCH 1/9] Issue #3152038 by sjoerdvandervis: create submodule to be able to export the user consent agreements to a CSV file. This is done by adding a new Action in a submodule, so that users are able to decide for themselves whether they want this functionality or not. If this submodule is enabled, a new field can be added to the Data Policy Agreements overview to be able to export the agreements to a CSV file. --- ....data_policy_export_data_policy_action.yml | 10 + .../data_policy_export.info.yml | 7 + .../data_policy_export.module | 40 +++ .../data_policy_export.services.yml | 4 + .../src/Plugin/Action/ExportDataPolicy.php | 239 ++++++++++++++++++ .../Annotation/DataPolicyExportPlugin.php | 40 +++ .../src/Plugin/DataPolicyExportPluginBase.php | 116 +++++++++ .../DataPolicyExportPluginInterface.php | 32 +++ .../Plugin/DataPolicyExportPluginManager.php | 32 +++ 9 files changed, 520 insertions(+) create mode 100644 modules/data_policy_export/config/install/system.action.data_policy_export_data_policy_action.yml create mode 100644 modules/data_policy_export/data_policy_export.info.yml create mode 100644 modules/data_policy_export/data_policy_export.module create mode 100644 modules/data_policy_export/data_policy_export.services.yml create mode 100644 modules/data_policy_export/src/Plugin/Action/ExportDataPolicy.php create mode 100644 modules/data_policy_export/src/Plugin/Annotation/DataPolicyExportPlugin.php create mode 100644 modules/data_policy_export/src/Plugin/DataPolicyExportPluginBase.php create mode 100644 modules/data_policy_export/src/Plugin/DataPolicyExportPluginInterface.php create mode 100644 modules/data_policy_export/src/Plugin/DataPolicyExportPluginManager.php diff --git a/modules/data_policy_export/config/install/system.action.data_policy_export_data_policy_action.yml b/modules/data_policy_export/config/install/system.action.data_policy_export_data_policy_action.yml new file mode 100644 index 0000000..4723b2c --- /dev/null +++ b/modules/data_policy_export/config/install/system.action.data_policy_export_data_policy_action.yml @@ -0,0 +1,10 @@ +langcode: en +status: true +dependencies: + module: + - data_policy_export +id: data_policy_export_data_policy_action +label: 'Export the selected data policies to CSV' +type: user_consent +plugin: data_policy_export_data_policy_action +configuration: { } diff --git a/modules/data_policy_export/data_policy_export.info.yml b/modules/data_policy_export/data_policy_export.info.yml new file mode 100644 index 0000000..d6588b7 --- /dev/null +++ b/modules/data_policy_export/data_policy_export.info.yml @@ -0,0 +1,7 @@ +name: Data Policy Export +description: Creates the possibility to export data policies to CSV as a bulk action. +core: 8.x +dependencies: + - views_bulk_operations + - csv_serialization:csv_serialization +type: module diff --git a/modules/data_policy_export/data_policy_export.module b/modules/data_policy_export/data_policy_export.module new file mode 100644 index 0000000..5982f3f --- /dev/null +++ b/modules/data_policy_export/data_policy_export.module @@ -0,0 +1,40 @@ +uriScheme($uri); + $target = file_uri_target($uri); + + // Get the file to see who the owner is. + $query = \Drupal::entityQuery('file'); + $query->condition('uri', $uri); + $fid = $query->execute(); + + /* @var \Drupal\file\FileInterface $file */ + $file = File::load(reset($fid)); + + $access = FALSE; + + // Allow access to users with correct permission or file owner. + if (\Drupal::currentUser()->hasPermission('administer users') || \Drupal::currentUser()->id() === $file->get('uid')->getString()) { + $access = TRUE; + } + + // The pattern should match all the declared file patterns from + // the `generateFilePath()` methods in export bulk actions plugins. + if ($scheme === 'private' && $access + && preg_match('/^csv\/export-data-policies-([a-f0-9]{12})\.csv$/i', $target)) { + return [ + 'Content-disposition' => 'attachment; filename="' . basename($target) . '"', + ]; + } +} diff --git a/modules/data_policy_export/data_policy_export.services.yml b/modules/data_policy_export/data_policy_export.services.yml new file mode 100644 index 0000000..2bcf118 --- /dev/null +++ b/modules/data_policy_export/data_policy_export.services.yml @@ -0,0 +1,4 @@ +services: + plugin.manager.data_policy_export_plugin: + class: Drupal\data_policy_export\Plugin\DataPolicyExportPluginManager + arguments: ['@container.namespaces', '@cache.discovery', '@module_handler', '@current_user', '@config.factory'] diff --git a/modules/data_policy_export/src/Plugin/Action/ExportDataPolicy.php b/modules/data_policy_export/src/Plugin/Action/ExportDataPolicy.php new file mode 100644 index 0000000..937ba0b --- /dev/null +++ b/modules/data_policy_export/src/Plugin/Action/ExportDataPolicy.php @@ -0,0 +1,239 @@ +dataPolicyExportPlugin = $dataPolicyExportPlugin; + $this->logger = $logger; + $this->currentUser = $currentUser; + $this->dateFormatter = $date_formatter; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static($configuration, $plugin_id, $plugin_definition, + $container->get('plugin.manager.data_policy_export_plugin'), + $container->get('logger.factory')->get('action'), + $container->get('current_user'), + $container->get('config.factory'), + $container->get('date.formatter') + ); + } + + /** + * {@inheritdoc} + */ + public function executeMultiple(array $entities) { + + // Check if headers exists. + if (empty($this->context['sandbox']['results']['headers'])) { + $this->context['sandbox']['results']['headers'] = ['Name', 'State', 'Datetime']; + } + + // Create the file if applicable. + if (empty($this->context['sandbox']['results']['file_path'])) { + // Store only the name relative to the output directory. On platforms such + // as Pantheon, different batch ticks can happen on different webheads. + // This can cause the file mount path to change, thus changing where on + // disk the tmp folder is actually located. + $this->context['sandbox']['results']['file_path'] = $this->generateFilePath(); + $file_path = $this->getBaseOutputDirectory() . DIRECTORY_SEPARATOR . $this->context['sandbox']['results']['file_path']; + + $csv = Writer::createFromPath($file_path, 'w'); + $csv->setDelimiter(','); + $csv->setEnclosure('"'); + $csv->setEscape('\\'); + + $csv->insertOne($this->context['sandbox']['results']['headers']); + } + else { + $file_path = $this->getBaseOutputDirectory() . DIRECTORY_SEPARATOR . $this->context['sandbox']['results']['file_path']; + $csv = Writer::createFromPath($file_path, 'a'); + } + + // Add formatter. + $csv->addFormatter([new CsvEncoder(), 'formatRow']); + + // Now add the entities to export. + foreach ($entities as $entity_id => $entity) { + $row = []; + $owner = $entity->getOwner(); + $ownerName = ''; + if ($owner) { + $ownerName = $owner->getDisplayName(); + } + $row[] = $ownerName; + $row[] = $this->getStateName($entity->state->value); + $row[] = $this->dateFormatter->format($entity->getChangedTime(), 'short'); + $csv->insertOne($row); + } + + if (($this->context['sandbox']['current_batch'] * $this->context['sandbox']['batch_size']) >= $this->context['sandbox']['total']) { + $data = @file_get_contents($file_path); + $name = basename($this->context['sandbox']['results']['file_path']); + $path = 'private://csv'; + + if (file_prepare_directory($path, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS) && (file_save_data($data, $path . '/' . $name))) { + $url = Url::fromUri(file_create_url($path . '/' . $name)); + $link = Link::fromTextAndUrl($this->t('Download file'), $url); + + $this->messenger()->addMessage($this->t('Export is complete. @link', [ + '@link' => $link->toString(), + ])); + } + else { + $this->messenger()->addMessage($this->t('Could not save the export file.'), 'error'); + $this->logger->error('Could not save the export file on: %name.', ['%name' => $name]); + } + } + } + + public function getStateName($state_id) { + $options = [ + UserConsentInterface::STATE_UNDECIDED => t('Undecided'), + UserConsentInterface::STATE_NOT_AGREE => t('Not agree'), + UserConsentInterface::STATE_AGREE => t('Agree'), + ]; + + return $options[$state_id]; + } + + /** + * {@inheritdoc} + */ + public function execute($object = NULL) { + $this->executeMultiple([$object]); + } + + /** + * {@inheritdoc} + */ + public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) { + /** @var \Drupal\data_policy\Entity\DataPolicyInterface $object */ + // TODO Check for export access instead. + return $object->access('view', $account, $return_as_object); + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + return $form; + } + + /** + * Returns the directory that forms the base for this exports file output. + * + * This method wraps file_directory_temp() to give inheriting classes the + * ability to use a different file system than the temporary file system. + * This was previously possible but was changed in #3075818. + * + * @return string + * The path to the Drupal directory that should be used for this export. + */ + protected function getBaseOutputDirectory() : string { + return file_directory_temp(); + } + + /** + * Returns a unique file path for this export. + * + * The returned path is relative to getBaseOutputDirectory(). This allows it + * to work on distributed systems where the temporary file path may change + * in between batch ticks. + * + * To make sure the file can be downloaded, the path must be declared in the + * download pattern of the social user export module. + * + * @see data_policy_export_file_download() + * + * @return string + * The path to the file. + */ + protected function generateFilePath() : string { + $hash = md5(microtime(TRUE)); + return 'export-data-policies-' . substr($hash, 20, 12) . '.csv'; + } + +} diff --git a/modules/data_policy_export/src/Plugin/Annotation/DataPolicyExportPlugin.php b/modules/data_policy_export/src/Plugin/Annotation/DataPolicyExportPlugin.php new file mode 100644 index 0000000..8a95a76 --- /dev/null +++ b/modules/data_policy_export/src/Plugin/Annotation/DataPolicyExportPlugin.php @@ -0,0 +1,40 @@ +entityTypeManager = $entity_type_manager; + $this->dateFormatter = $date_formatter; + $this->database = $database; + } + + /** + * The create method. + * + * @param \Symfony\Component\DependencyInjection\ContainerInterface $container + * Container interface. + * @param array $configuration + * An array of configuration. + * @param string $plugin_id + * The plugin id. + * @param mixed $plugin_definition + * The plugin definition. + * + * @return \Drupal\Core\Plugin\ContainerFactoryPluginInterface|\Drupal\data_policy_export\Plugin\DataPolicyExportPluginBase + * Returns the DataPolicyExportPluginBase. + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('entity_type.manager'), + $container->get('date.formatter'), + $container->get('database') + ); + } + + /** + * Returns the header. + * + * @return \Drupal\Core\StringTranslation\TranslatableMarkup|string + * The header. + */ + public function getHeader() { + return ''; + } + + /** + * Returns the value. + * + * @param \Drupal\data_policy\Entity\DataPolicyInterface $entity + * The Data Policy entity to get the value from. + * + * @return string + * The value. + */ + public function getValue(DataPolicyInterface $entity) { + return ''; + } + +} diff --git a/modules/data_policy_export/src/Plugin/DataPolicyExportPluginInterface.php b/modules/data_policy_export/src/Plugin/DataPolicyExportPluginInterface.php new file mode 100644 index 0000000..f909f5e --- /dev/null +++ b/modules/data_policy_export/src/Plugin/DataPolicyExportPluginInterface.php @@ -0,0 +1,32 @@ +alterInfo('data_policy_export_plugin_info'); + $this->setCacheBackend($cache_backend, 'data_policy_export_data_policy_export_plugin_plugins'); + } + +} From 6f6ebaaa8487077d97ba210db6fb12310fc2ec74 Mon Sep 17 00:00:00 2001 From: Sjoerd van der Vis Date: Thu, 18 Jun 2020 12:31:21 +0200 Subject: [PATCH 2/9] Issue #3152038 by sjoerdvandervis: Create the install and uninstall hooks and put the correct configuration including views_bulk_operations in place --- .../config/update/data_policy_export_view.yml | 641 ++++++++++++++++++ .../data_policy_export.install | 43 ++ 2 files changed, 684 insertions(+) create mode 100644 modules/data_policy_export/config/update/data_policy_export_view.yml create mode 100644 modules/data_policy_export/data_policy_export.install diff --git a/modules/data_policy_export/config/update/data_policy_export_view.yml b/modules/data_policy_export/config/update/data_policy_export_view.yml new file mode 100644 index 0000000..ff35dca --- /dev/null +++ b/modules/data_policy_export/config/update/data_policy_export_view.yml @@ -0,0 +1,641 @@ +langcode: en +status: true +dependencies: + config: + - system.menu.admin + module: + - data_policy + - user + - views_bulk_operations +id: data_policy_agreements +label: 'Data Policy Agreements' +module: views +description: '' +tag: '' +base_table: user_consent +base_field: id +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: perm + options: + perm: 'overview user consents' + cache: + type: none + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: mini + options: + items_per_page: 50 + offset: 0 + id: 0 + total_pages: null + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + tags: + previous: ‹‹ + next: ›› + style: + type: table + options: + grouping: { } + row_class: '' + default_row_class: true + override: true + sticky: false + caption: '' + summary: '' + description: '' + columns: + data_policy_revision_id: data_policy_revision_id + user_id: user_id + state: state + changed: changed + info: + data_policy_revision_id: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + user_id: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + state: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + changed: + sortable: true + default_sort_order: desc + align: '' + separator: '' + empty_column: false + responsive: '' + default: changed + empty_table: true + row: + type: fields + fields: + views_bulk_operations_bulk_form: + id: views_bulk_operations_bulk_form + table: views + field: views_bulk_operations_bulk_form + relationship: none + group_type: group + admin_label: '' + label: 'Views bulk operations' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + batch: true + batch_size: 25 + form_step: true + buttons: true + clear_on_exposed: false + action_title: Action + selected_actions: + comment_delete_action: 0 + data_policy_export_data_policy_action: data_policy_export_data_policy_action + views_bulk_operations_delete_entity: 0 + pathauto_update_alias: 0 + 'entity:save_action:user_consent': 0 + preconfiguration: + data_policy_export_data_policy_action: + label_override: '' + plugin_id: views_bulk_operations_bulk_form + data_policy_revision_id: + id: data_policy_revision_id + table: user_consent + field: data_policy_revision_id + relationship: none + group_type: group + admin_label: '' + label: 'Data policy revision' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + date_format: fallback + custom_date_format: '' + timezone: '' + click_sort_column: value + type: number_integer + settings: + thousand_separator: '' + prefix_suffix: true + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: user_consent + entity_field: data_policy_revision_id + plugin_id: field + user_id: + id: user_id + table: user_consent + field: user_id + relationship: none + group_type: group + admin_label: '' + label: User + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: target_id + type: entity_reference_label + settings: + link: true + group_column: target_id + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: user_consent + entity_field: user_id + plugin_id: field + state: + id: state + table: user_consent + field: state + relationship: none + group_type: group + admin_label: '' + label: State + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: number_integer + settings: + thousand_separator: '' + prefix_suffix: true + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: user_consent + entity_field: state + plugin_id: field + changed: + id: changed + table: user_consent + field: changed + relationship: none + group_type: group + admin_label: '' + label: 'Date and time' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: timestamp + settings: + date_format: medium + custom_date_format: '' + timezone: '' + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: user_consent + entity_field: changed + plugin_id: field + filters: + data_policy_revision_id: + id: data_policy_revision_id + table: user_consent + field: data_policy_revision_id + relationship: none + group_type: group + admin_label: '' + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: '' + label: 'Data policy revision' + description: '' + use_operator: false + operator: data_policy_revision_id_op + identifier: data-policy-revision + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + contentmanager: '0' + sitemanager: '0' + reduce: 0 + placeholder: '' + min_placeholder: '' + max_placeholder: '' + operator_limit_selection: false + operator_list: { } + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + entity_type: user_consent + entity_field: data_policy_revision_id + plugin_id: numeric + state: + id: state + table: user_consent + field: state + relationship: none + group_type: group + admin_label: '' + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: state_op + label: State + description: '' + use_operator: false + operator: state_op + identifier: state + required: false + remember: false + multiple: true + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + contentmanager: '0' + sitemanager: '0' + reduce: 0 + placeholder: '' + min_placeholder: '' + max_placeholder: '' + operator_limit_selection: false + operator_list: { } + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + entity_type: user_consent + entity_field: state + plugin_id: numeric + status: + id: status + table: user_consent + field: status + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: '1' + group: 1 + exposed: false + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: '' + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + operator_limit_selection: false + operator_list: { } + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + entity_type: user_consent + entity_field: status + plugin_id: boolean + sorts: { } + title: 'Data Policy Agreements' + header: { } + footer: { } + empty: + area: + id: area + table: views + field: area + relationship: none + group_type: group + admin_label: '' + empty: true + tokenize: false + content: + value: 'User consents not found.' + format: basic_html + plugin_id: text + relationships: { } + arguments: { } + display_extenders: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user.permissions + tags: { } + page: + display_plugin: page + id: page + display_title: Page + position: 1 + display_options: + display_extenders: { } + path: admin/reports/data-policy-agreements + menu: + type: normal + title: 'Data Policy Agreements' + description: '' + expanded: false + parent: system.admin_reports + weight: 0 + context: '0' + menu_name: admin + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user.permissions + tags: { } diff --git a/modules/data_policy_export/data_policy_export.install b/modules/data_policy_export/data_policy_export.install new file mode 100644 index 0000000..644e703 --- /dev/null +++ b/modules/data_policy_export/data_policy_export.install @@ -0,0 +1,43 @@ +getEditable('views.view.data_policy_agreements'); + $config->setData($settings)->save(TRUE); + } + } +} + +/** + * Implements hook_uninstall(). + */ +function data_policy_export_uninstall() { + // When uninstalling this module, we would like to place back the original + // configuration. + $config_file = drupal_get_path('module', 'data_policy') . '/config/install/views.view.data_policy_agreements.yml'; + + if (is_file($config_file)) { + $settings = Yaml::parse(file_get_contents($config_file)); + if (is_array($settings)) { + $config = \Drupal::configFactory() + ->getEditable('views.view.data_policy_agreements'); + $config->setData($settings)->save(TRUE); + } + } +} From ce1b0ae7ef4c1abdbcc3ddb04ad98f1485815ee0 Mon Sep 17 00:00:00 2001 From: sjoerdvandervis <19951173+sjoerdvandervis@users.noreply.github.com> Date: Thu, 18 Jun 2020 14:17:18 +0200 Subject: [PATCH 3/9] Update data_policy_export.info.yml --- modules/data_policy_export/data_policy_export.info.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/data_policy_export/data_policy_export.info.yml b/modules/data_policy_export/data_policy_export.info.yml index d6588b7..79bf857 100644 --- a/modules/data_policy_export/data_policy_export.info.yml +++ b/modules/data_policy_export/data_policy_export.info.yml @@ -4,4 +4,5 @@ core: 8.x dependencies: - views_bulk_operations - csv_serialization:csv_serialization + - data_policy type: module From 40ce49cfa49553189aeb10080108135cd3273a08 Mon Sep 17 00:00:00 2001 From: Sjoerd van der Vis Date: Fri, 19 Jun 2020 12:05:36 +0200 Subject: [PATCH 4/9] Issue #3152038 by sjoerdvandervis: Alter our install hooks to only install / uninstall our new config and remove the obsolete views file --- .../config/update/data_policy_export_view.yml | 641 ------------------ .../data_policy_export.install | 105 ++- 2 files changed, 83 insertions(+), 663 deletions(-) delete mode 100644 modules/data_policy_export/config/update/data_policy_export_view.yml diff --git a/modules/data_policy_export/config/update/data_policy_export_view.yml b/modules/data_policy_export/config/update/data_policy_export_view.yml deleted file mode 100644 index ff35dca..0000000 --- a/modules/data_policy_export/config/update/data_policy_export_view.yml +++ /dev/null @@ -1,641 +0,0 @@ -langcode: en -status: true -dependencies: - config: - - system.menu.admin - module: - - data_policy - - user - - views_bulk_operations -id: data_policy_agreements -label: 'Data Policy Agreements' -module: views -description: '' -tag: '' -base_table: user_consent -base_field: id -core: 8.x -display: - default: - display_plugin: default - id: default - display_title: Master - position: 0 - display_options: - access: - type: perm - options: - perm: 'overview user consents' - cache: - type: none - options: { } - query: - type: views_query - options: - disable_sql_rewrite: false - distinct: false - replica: false - query_comment: '' - query_tags: { } - exposed_form: - type: basic - options: - submit_button: Apply - reset_button: false - reset_button_label: Reset - exposed_sorts_label: 'Sort by' - expose_sort_order: true - sort_asc_label: Asc - sort_desc_label: Desc - pager: - type: mini - options: - items_per_page: 50 - offset: 0 - id: 0 - total_pages: null - expose: - items_per_page: false - items_per_page_label: 'Items per page' - items_per_page_options: '5, 10, 25, 50' - items_per_page_options_all: false - items_per_page_options_all_label: '- All -' - offset: false - offset_label: Offset - tags: - previous: ‹‹ - next: ›› - style: - type: table - options: - grouping: { } - row_class: '' - default_row_class: true - override: true - sticky: false - caption: '' - summary: '' - description: '' - columns: - data_policy_revision_id: data_policy_revision_id - user_id: user_id - state: state - changed: changed - info: - data_policy_revision_id: - sortable: false - default_sort_order: asc - align: '' - separator: '' - empty_column: false - responsive: '' - user_id: - sortable: true - default_sort_order: asc - align: '' - separator: '' - empty_column: false - responsive: '' - state: - sortable: true - default_sort_order: asc - align: '' - separator: '' - empty_column: false - responsive: '' - changed: - sortable: true - default_sort_order: desc - align: '' - separator: '' - empty_column: false - responsive: '' - default: changed - empty_table: true - row: - type: fields - fields: - views_bulk_operations_bulk_form: - id: views_bulk_operations_bulk_form - table: views - field: views_bulk_operations_bulk_form - relationship: none - group_type: group - admin_label: '' - label: 'Views bulk operations' - exclude: false - alter: - alter_text: false - text: '' - make_link: false - path: '' - absolute: false - external: false - replace_spaces: false - path_case: none - trim_whitespace: false - alt: '' - rel: '' - link_class: '' - prefix: '' - suffix: '' - target: '' - nl2br: false - max_length: 0 - word_boundary: true - ellipsis: true - more_link: false - more_link_text: '' - more_link_path: '' - strip_tags: false - trim: false - preserve_tags: '' - html: false - element_type: '' - element_class: '' - element_label_type: '' - element_label_class: '' - element_label_colon: true - element_wrapper_type: '' - element_wrapper_class: '' - element_default_classes: true - empty: '' - hide_empty: false - empty_zero: false - hide_alter_empty: true - batch: true - batch_size: 25 - form_step: true - buttons: true - clear_on_exposed: false - action_title: Action - selected_actions: - comment_delete_action: 0 - data_policy_export_data_policy_action: data_policy_export_data_policy_action - views_bulk_operations_delete_entity: 0 - pathauto_update_alias: 0 - 'entity:save_action:user_consent': 0 - preconfiguration: - data_policy_export_data_policy_action: - label_override: '' - plugin_id: views_bulk_operations_bulk_form - data_policy_revision_id: - id: data_policy_revision_id - table: user_consent - field: data_policy_revision_id - relationship: none - group_type: group - admin_label: '' - label: 'Data policy revision' - exclude: false - alter: - alter_text: false - text: '' - make_link: false - path: '' - absolute: false - external: false - replace_spaces: false - path_case: none - trim_whitespace: false - alt: '' - rel: '' - link_class: '' - prefix: '' - suffix: '' - target: '' - nl2br: false - max_length: 0 - word_boundary: true - ellipsis: true - more_link: false - more_link_text: '' - more_link_path: '' - strip_tags: false - trim: false - preserve_tags: '' - html: false - element_type: '' - element_class: '' - element_label_type: '' - element_label_class: '' - element_label_colon: true - element_wrapper_type: '' - element_wrapper_class: '' - element_default_classes: true - empty: '' - hide_empty: false - empty_zero: false - hide_alter_empty: true - date_format: fallback - custom_date_format: '' - timezone: '' - click_sort_column: value - type: number_integer - settings: - thousand_separator: '' - prefix_suffix: true - group_column: value - group_columns: { } - group_rows: true - delta_limit: 0 - delta_offset: 0 - delta_reversed: false - delta_first_last: false - multi_type: separator - separator: ', ' - field_api_classes: false - entity_type: user_consent - entity_field: data_policy_revision_id - plugin_id: field - user_id: - id: user_id - table: user_consent - field: user_id - relationship: none - group_type: group - admin_label: '' - label: User - exclude: false - alter: - alter_text: false - text: '' - make_link: false - path: '' - absolute: false - external: false - replace_spaces: false - path_case: none - trim_whitespace: false - alt: '' - rel: '' - link_class: '' - prefix: '' - suffix: '' - target: '' - nl2br: false - max_length: 0 - word_boundary: true - ellipsis: true - more_link: false - more_link_text: '' - more_link_path: '' - strip_tags: false - trim: false - preserve_tags: '' - html: false - element_type: '' - element_class: '' - element_label_type: '' - element_label_class: '' - element_label_colon: true - element_wrapper_type: '' - element_wrapper_class: '' - element_default_classes: true - empty: '' - hide_empty: false - empty_zero: false - hide_alter_empty: true - click_sort_column: target_id - type: entity_reference_label - settings: - link: true - group_column: target_id - group_columns: { } - group_rows: true - delta_limit: 0 - delta_offset: 0 - delta_reversed: false - delta_first_last: false - multi_type: separator - separator: ', ' - field_api_classes: false - entity_type: user_consent - entity_field: user_id - plugin_id: field - state: - id: state - table: user_consent - field: state - relationship: none - group_type: group - admin_label: '' - label: State - exclude: false - alter: - alter_text: false - text: '' - make_link: false - path: '' - absolute: false - external: false - replace_spaces: false - path_case: none - trim_whitespace: false - alt: '' - rel: '' - link_class: '' - prefix: '' - suffix: '' - target: '' - nl2br: false - max_length: 0 - word_boundary: true - ellipsis: true - more_link: false - more_link_text: '' - more_link_path: '' - strip_tags: false - trim: false - preserve_tags: '' - html: false - element_type: '' - element_class: '' - element_label_type: '' - element_label_class: '' - element_label_colon: true - element_wrapper_type: '' - element_wrapper_class: '' - element_default_classes: true - empty: '' - hide_empty: false - empty_zero: false - hide_alter_empty: true - click_sort_column: value - type: number_integer - settings: - thousand_separator: '' - prefix_suffix: true - group_column: value - group_columns: { } - group_rows: true - delta_limit: 0 - delta_offset: 0 - delta_reversed: false - delta_first_last: false - multi_type: separator - separator: ', ' - field_api_classes: false - entity_type: user_consent - entity_field: state - plugin_id: field - changed: - id: changed - table: user_consent - field: changed - relationship: none - group_type: group - admin_label: '' - label: 'Date and time' - exclude: false - alter: - alter_text: false - text: '' - make_link: false - path: '' - absolute: false - external: false - replace_spaces: false - path_case: none - trim_whitespace: false - alt: '' - rel: '' - link_class: '' - prefix: '' - suffix: '' - target: '' - nl2br: false - max_length: 0 - word_boundary: true - ellipsis: true - more_link: false - more_link_text: '' - more_link_path: '' - strip_tags: false - trim: false - preserve_tags: '' - html: false - element_type: '' - element_class: '' - element_label_type: '' - element_label_class: '' - element_label_colon: true - element_wrapper_type: '' - element_wrapper_class: '' - element_default_classes: true - empty: '' - hide_empty: false - empty_zero: false - hide_alter_empty: true - click_sort_column: value - type: timestamp - settings: - date_format: medium - custom_date_format: '' - timezone: '' - group_column: value - group_columns: { } - group_rows: true - delta_limit: 0 - delta_offset: 0 - delta_reversed: false - delta_first_last: false - multi_type: separator - separator: ', ' - field_api_classes: false - entity_type: user_consent - entity_field: changed - plugin_id: field - filters: - data_policy_revision_id: - id: data_policy_revision_id - table: user_consent - field: data_policy_revision_id - relationship: none - group_type: group - admin_label: '' - operator: in - value: { } - group: 1 - exposed: true - expose: - operator_id: '' - label: 'Data policy revision' - description: '' - use_operator: false - operator: data_policy_revision_id_op - identifier: data-policy-revision - required: false - remember: false - multiple: false - remember_roles: - authenticated: authenticated - anonymous: '0' - administrator: '0' - contentmanager: '0' - sitemanager: '0' - reduce: 0 - placeholder: '' - min_placeholder: '' - max_placeholder: '' - operator_limit_selection: false - operator_list: { } - is_grouped: false - group_info: - label: '' - description: '' - identifier: '' - optional: true - widget: select - multiple: false - remember: false - default_group: All - default_group_multiple: { } - group_items: { } - entity_type: user_consent - entity_field: data_policy_revision_id - plugin_id: numeric - state: - id: state - table: user_consent - field: state - relationship: none - group_type: group - admin_label: '' - operator: in - value: { } - group: 1 - exposed: true - expose: - operator_id: state_op - label: State - description: '' - use_operator: false - operator: state_op - identifier: state - required: false - remember: false - multiple: true - remember_roles: - authenticated: authenticated - anonymous: '0' - administrator: '0' - contentmanager: '0' - sitemanager: '0' - reduce: 0 - placeholder: '' - min_placeholder: '' - max_placeholder: '' - operator_limit_selection: false - operator_list: { } - is_grouped: false - group_info: - label: '' - description: '' - identifier: '' - optional: true - widget: select - multiple: false - remember: false - default_group: All - default_group_multiple: { } - group_items: { } - entity_type: user_consent - entity_field: state - plugin_id: numeric - status: - id: status - table: user_consent - field: status - relationship: none - group_type: group - admin_label: '' - operator: '=' - value: '1' - group: 1 - exposed: false - expose: - operator_id: '' - label: '' - description: '' - use_operator: false - operator: '' - identifier: '' - required: false - remember: false - multiple: false - remember_roles: - authenticated: authenticated - operator_limit_selection: false - operator_list: { } - is_grouped: false - group_info: - label: '' - description: '' - identifier: '' - optional: true - widget: select - multiple: false - remember: false - default_group: All - default_group_multiple: { } - group_items: { } - entity_type: user_consent - entity_field: status - plugin_id: boolean - sorts: { } - title: 'Data Policy Agreements' - header: { } - footer: { } - empty: - area: - id: area - table: views - field: area - relationship: none - group_type: group - admin_label: '' - empty: true - tokenize: false - content: - value: 'User consents not found.' - format: basic_html - plugin_id: text - relationships: { } - arguments: { } - display_extenders: { } - cache_metadata: - max-age: -1 - contexts: - - 'languages:language_content' - - 'languages:language_interface' - - url - - url.query_args - - user.permissions - tags: { } - page: - display_plugin: page - id: page - display_title: Page - position: 1 - display_options: - display_extenders: { } - path: admin/reports/data-policy-agreements - menu: - type: normal - title: 'Data Policy Agreements' - description: '' - expanded: false - parent: system.admin_reports - weight: 0 - context: '0' - menu_name: admin - cache_metadata: - max-age: -1 - contexts: - - 'languages:language_content' - - 'languages:language_interface' - - url - - url.query_args - - user.permissions - tags: { } diff --git a/modules/data_policy_export/data_policy_export.install b/modules/data_policy_export/data_policy_export.install index 644e703..43c49c2 100644 --- a/modules/data_policy_export/data_policy_export.install +++ b/modules/data_policy_export/data_policy_export.install @@ -11,33 +11,94 @@ use Symfony\Component\Yaml\Yaml; * Implements hook_install(). */ function data_policy_export_install() { - // Set our adjusted views data as the new config. - $config_file = drupal_get_path('module', 'data_policy_export') . '/config/update/data_policy_export_view.yml'; + $config_yaml = <<getEditable('views.view.data_policy_agreements'); - $config->setData($settings)->save(TRUE); - } - } +YAML; + + // Parse the above config for our new field. + $vbo_field_config = Yaml::parse($config_yaml); + + $config = \Drupal::configFactory() + ->getEditable('views.view.data_policy_agreements'); + // Get the config of the fields. + $fields = $config->get('display.default.display_options.fields'); + // Add up the two field configs. + $fields = $vbo_field_config + $fields; + // Set the fields in the config. + $config->set('display.default.display_options.fields', $fields)->save(); } /** * Implements hook_uninstall(). */ function data_policy_export_uninstall() { - // When uninstalling this module, we would like to place back the original - // configuration. - $config_file = drupal_get_path('module', 'data_policy') . '/config/install/views.view.data_policy_agreements.yml'; - - if (is_file($config_file)) { - $settings = Yaml::parse(file_get_contents($config_file)); - if (is_array($settings)) { - $config = \Drupal::configFactory() - ->getEditable('views.view.data_policy_agreements'); - $config->setData($settings)->save(TRUE); - } - } + // When uninstalling this module, we would like to remove the field that + // was added when installing this module. + $config = \Drupal::configFactory() + ->getEditable('views.view.data_policy_agreements'); + $config->clear('display.default.display_options.fields.views_bulk_operations_bulk_form')->save(TRUE); } From 72d8ad197a433d59f86357cac8a3e1a51b1358d7 Mon Sep 17 00:00:00 2001 From: Sjoerd van der Vis Date: Fri, 19 Jun 2020 14:09:57 +0200 Subject: [PATCH 5/9] Issue #3152038 by sjoerdvandervis: Coding standards --- .../src/Plugin/Action/ExportDataPolicy.php | 16 +++++++++++++++- .../src/Plugin/DataPolicyExportPluginBase.php | 1 - 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/modules/data_policy_export/src/Plugin/Action/ExportDataPolicy.php b/modules/data_policy_export/src/Plugin/Action/ExportDataPolicy.php index 937ba0b..42f5638 100644 --- a/modules/data_policy_export/src/Plugin/Action/ExportDataPolicy.php +++ b/modules/data_policy_export/src/Plugin/Action/ExportDataPolicy.php @@ -78,6 +78,8 @@ class ExportDataPolicy extends ViewsBulkOperationsActionBase implements Containe * The current user account. * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory * Config factory for the export plugin access. + * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter + * Date formatter to be able to format the date to human-friendly. */ public function __construct(array $configuration, $plugin_id, $plugin_definition, DataPolicyExportPluginManager $dataPolicyExportPlugin, LoggerInterface $logger, AccountProxyInterface $currentUser, ConfigFactoryInterface $configFactory, DateFormatterInterface $date_formatter) { parent::__construct($configuration, $plugin_id, $plugin_definition); @@ -108,7 +110,11 @@ public function executeMultiple(array $entities) { // Check if headers exists. if (empty($this->context['sandbox']['results']['headers'])) { - $this->context['sandbox']['results']['headers'] = ['Name', 'State', 'Datetime']; + $this->context['sandbox']['results']['headers'] = [ + 'Name', + 'State', + 'Datetime', + ]; } // Create the file if applicable. @@ -169,6 +175,14 @@ public function executeMultiple(array $entities) { } } + /** + * Helper function to return the human-friendly name to the CSV export. + * + * @param $state_id + * The ID of the state for which we want to get the text. + * @return mixed + * The text we will be using in the export. + */ public function getStateName($state_id) { $options = [ UserConsentInterface::STATE_UNDECIDED => t('Undecided'), diff --git a/modules/data_policy_export/src/Plugin/DataPolicyExportPluginBase.php b/modules/data_policy_export/src/Plugin/DataPolicyExportPluginBase.php index 2384b8d..f6c5b80 100644 --- a/modules/data_policy_export/src/Plugin/DataPolicyExportPluginBase.php +++ b/modules/data_policy_export/src/Plugin/DataPolicyExportPluginBase.php @@ -9,7 +9,6 @@ use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\data_policy\Entity\DataPolicyInterface; -use Drupal\data_policy_export\Plugin\DataPolicyExportPluginInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** From a5da1fb13bd9b6558a0e85050b8365e355b5d770 Mon Sep 17 00:00:00 2001 From: Sjoerd van der Vis Date: Fri, 19 Jun 2020 14:30:31 +0200 Subject: [PATCH 6/9] Issue #3152038 by sjoerdvandervis: Coding standards --- .../data_policy_export/src/Plugin/Action/ExportDataPolicy.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/data_policy_export/src/Plugin/Action/ExportDataPolicy.php b/modules/data_policy_export/src/Plugin/Action/ExportDataPolicy.php index 42f5638..02fe67a 100644 --- a/modules/data_policy_export/src/Plugin/Action/ExportDataPolicy.php +++ b/modules/data_policy_export/src/Plugin/Action/ExportDataPolicy.php @@ -178,8 +178,9 @@ public function executeMultiple(array $entities) { /** * Helper function to return the human-friendly name to the CSV export. * - * @param $state_id + * @param int $state_id * The ID of the state for which we want to get the text. + * * @return mixed * The text we will be using in the export. */ From aa1e63701deabc836909b5090f52e9defdde7865 Mon Sep 17 00:00:00 2001 From: Sjoerd van der Vis Date: Tue, 23 Jun 2020 15:43:25 +0200 Subject: [PATCH 7/9] Implement label() function in the UserConsent entity to return the display name when rendering in VBO --- src/Entity/UserConsent.php | 13 +++++++++++++ src/Entity/UserConsentInterface.php | 5 +++++ 2 files changed, 18 insertions(+) diff --git a/src/Entity/UserConsent.php b/src/Entity/UserConsent.php index 2f160b0..73d136c 100644 --- a/src/Entity/UserConsent.php +++ b/src/Entity/UserConsent.php @@ -184,4 +184,17 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { return $fields; } + /** + * Override of the default label() function to return a human-friendly name. + * @return \Drupal\Component\Render\MarkupInterface|mixed|string|null + */ + public function label() { + $user = $this->getOwner(); + if ($user && $user->getDisplayName()) { + return $user->getDisplayName(); + } + + return ''; + } + } diff --git a/src/Entity/UserConsentInterface.php b/src/Entity/UserConsentInterface.php index fc2e541..6761ef4 100644 --- a/src/Entity/UserConsentInterface.php +++ b/src/Entity/UserConsentInterface.php @@ -99,4 +99,9 @@ public function setPublished($published); */ public function setRevision(DataPolicyInterface $data_policy); + /** + * @return string|null + */ + public function label(); + } From 89049849e11bc75e9fc23f19ccf5b33be454c363 Mon Sep 17 00:00:00 2001 From: Sjoerd van der Vis Date: Tue, 23 Jun 2020 17:22:26 +0200 Subject: [PATCH 8/9] Issue #3152038 by sjoerdvandervis: Change dependencies to reflect the actual state --- .../system.action.data_policy_export_data_policy_action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/data_policy_export/config/install/system.action.data_policy_export_data_policy_action.yml b/modules/data_policy_export/config/install/system.action.data_policy_export_data_policy_action.yml index 4723b2c..10f9ea9 100644 --- a/modules/data_policy_export/config/install/system.action.data_policy_export_data_policy_action.yml +++ b/modules/data_policy_export/config/install/system.action.data_policy_export_data_policy_action.yml @@ -2,7 +2,7 @@ langcode: en status: true dependencies: module: - - data_policy_export + - views_bulk_operations id: data_policy_export_data_policy_action label: 'Export the selected data policies to CSV' type: user_consent From 3a819dd36e99fadd04ab547dff6d6c1b2c23f869 Mon Sep 17 00:00:00 2001 From: Sjoerd van der Vis Date: Tue, 23 Jun 2020 18:04:10 +0200 Subject: [PATCH 9/9] Increase batch size to process items faster --- modules/data_policy_export/data_policy_export.install | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/data_policy_export/data_policy_export.install b/modules/data_policy_export/data_policy_export.install index 43c49c2..819e27b 100644 --- a/modules/data_policy_export/data_policy_export.install +++ b/modules/data_policy_export/data_policy_export.install @@ -61,7 +61,7 @@ views_bulk_operations_bulk_form: empty_zero: false hide_alter_empty: true batch: true - batch_size: 25 + batch_size: 250 form_step: true buttons: true clear_on_exposed: false