Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
c2a548a
Fix: Address lookup caching.
Adnan-cds Mar 7, 2024
602ec2d
Feat: Long term storage.
Adnan-cds Mar 15, 2024
305cd76
Feat: Long term storage.
Adnan-cds Mar 22, 2024
349949f
Feat: Long term storage.
Adnan-cds May 23, 2024
094618f
Fix: Long term storage.
Adnan-cds May 24, 2024
8939994
Feat: Long term storage.
Adnan-cds May 24, 2024
375d5d0
Merge remote-tracking branch 'origin/1.x' into feature/long-term-storage
Adnan-cds May 24, 2024
5314964
Feat: Long term storage.
Adnan-cds May 31, 2024
f25924a
Fix: Long term storage.
Adnan-cds May 31, 2024
1c18230
Feat: Textarea PII redaction.
Adnan-cds Aug 16, 2024
042fb09
Merge remote-tracking branch 'origin/1.x' into feature/long-term-storage
Adnan-cds Sep 12, 2024
e507132
Test: Automated tests.
Adnan-cds Sep 20, 2024
38c9adf
Chore: Coding standard fixes.
Adnan-cds Sep 20, 2024
ddebdbc
Chore: More coding standard fixes.
Adnan-cds Sep 20, 2024
aedf440
Chore: More coding standard fixes.
Adnan-cds Sep 21, 2024
a431c1c
Doc: Better docblock comments for mock database driver.
Adnan-cds Sep 21, 2024
f598c5d
Merge remote-tracking branch 'origin/1.x' into fix/address-lookup-cac…
Oct 11, 2024
a54392e
Test: Address lookup caching.
Adnan-cds Oct 11, 2024
5301517
Add missing comma.
finnlewis Oct 17, 2024
fefbb76
Add specific notes on setting up and testing in DDEV.
finnlewis Oct 17, 2024
c5e953c
Fix simple coding standards issues.
finnlewis Oct 17, 2024
3d63643
Merge pull request #97 from localgovdrupal/feature/long-term-storage-…
finnlewis Oct 17, 2024
c50881d
Fix: Address lookup validation.
Adnan-cds Oct 18, 2024
52d5d4c
Merge pull request #70 from localgovdrupal/fix/address-lookup-caching
Adnan-cds Oct 22, 2024
1ba5ec1
Test: Address lookup validation.
Adnan-cds Oct 22, 2024
6ad87e6
Merge remote-tracking branch 'origin/1.x' into fix/address-lookup-error
Adnan-cds Oct 22, 2024
99f5ccb
Fix: Address selection errors.
Adnan-cds Oct 24, 2024
c5c94c7
Fix: Error message text.
Adnan-cds Oct 25, 2024
0e54ae6
Merge remote-tracking branch 'origin/1.x' into feature/long-term-storage
Adnan-cds Oct 25, 2024
2b38ffb
Feat: PII redaction plugin.
Adnan-cds Oct 25, 2024
b73ec5a
Merge remote-tracking branch 'origin/feature/long-term-storage' into …
Adnan-cds Oct 25, 2024
64869f5
Feat: PII redaction plugin.
Adnan-cds Oct 31, 2024
16dc035
Feat: Drush command.
Adnan-cds Nov 1, 2024
30a8a36
Feat: LTS config form.
Adnan-cds Nov 1, 2024
0ef2a26
Chore: Replaced a few reused strings with constants.
Adnan-cds Nov 4, 2024
1ecc466
Doc: Describing optional PII redaction functionality.
Adnan-cds Nov 4, 2024
b24fb97
Chore: Sentence case for subelement label.
Adnan-cds Nov 5, 2024
6a4b52a
Feat: Custom error messages for Address lookup element.
Adnan-cds Nov 7, 2024
f9a7e8d
Fix: Address lookup element.
Adnan-cds Nov 8, 2024
3dd9341
Merge pull request #98 from localgovdrupal/fix/address-lookup-error
Adnan-cds Nov 20, 2024
adc94e6
Merge pull request #95 from localgovdrupal/feature/long-term-storage
Adnan-cds Nov 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ Provides additional configuration, styling and components for the Drupal Webform
* LocalGov Forms Date - A date input field based on the [GDS Date Input pattern](https://design-system.service.gov.uk/components/date-input/)
* LocalGov address lookup - Webform element with a configurable address lookup backend. Geocoder plugins act as backends.

## Plugins
- Personally Identifiable Information (PII) redactor from Webform submissions: At the moment, a plugin manager `plugin.manager.pii_redactor` and a sample plugin are provided.

## Dependencies
The geocoder-php/nominatim-provider package is necessary to run automated tests:
```
Expand All @@ -23,4 +26,3 @@ To avoid the configuration being removed by deployments, install the [Config ign
webform.webform.*
webform.webform_options.*
```

5 changes: 3 additions & 2 deletions js/address_change.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
* Populate Central Hub Selected Address
*
* Adds the selected address to drupalSettings.centralhub.selectedAddress.
*
* @param {jQuery} selectList
* Address selectlist element.
*/
Expand Down Expand Up @@ -87,7 +88,7 @@
var searchButton = addressLookupElement.find('.js-address-searchbutton');
var selectListContainer = addressLookupElement.find('.js-address-select-container');
var selectList = selectListContainer.find('.js-address-select');
var error = selectListContainer.find('.js-address-error');
var error = selectListContainer.find('.js-address-error, .error');

// Change the search button to normal button.
searchButton.attr('type', 'button');
Expand Down Expand Up @@ -154,7 +155,7 @@
var searchButton = addressLookupElement.find('.js-address-searchbutton');
var selectListContainer = addressLookupElement.find('.js-address-select-container');
var selectList = selectListContainer.find('.js-address-select');
var error = selectListContainer.find('.js-address-error');
var error = selectListContainer.find('.js-address-error, .error');
var resetButton = addressLookupElement.find('.js-reset-address');
var manualButton = addressLookupElement.find('.js-manual-address');
var manualAddressContainer = addressLookupElement.find('+ .js-address-entry-container');
Expand Down
13 changes: 1 addition & 12 deletions js/address_select.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
function hideManualAddress(centralHubElement, type, settings) {
var manualAddressContainer = centralHubElement.find('.js-address-entry-container');
var manualButton = centralHubElement.find('.js-manual-address');
var addressSelectContainer = centralHubElement.find('.js-address-select-container');
manualAddressContainer.addClass('hidden');

if (type == 'hard') {
Expand All @@ -63,24 +62,18 @@

/**
* Show the manual address form elements.
*
* @param {jQuery} centralHubElement
* Centralhub address element.
*/
function showManualAddress(centralHubElement) {
var manualAddressContainer = centralHubElement.find('.js-address-entry-container');
var manualButton = centralHubElement.find('.js-manual-address');
var addressSelectContainer = centralHubElement.find('.js-address-select-container');
var addressSelect = addressSelectContainer.find('select');
var searchElement = centralHubElement.find('.js-address-searchstring');
var addressError = addressSelectContainer.find('.js-address-error');
manualAddressContainer.removeClass('hidden');
// manualAddressContainer.find('input').val('');
manualButton.hide();
// addressSelectContainer.addClass('hidden');
// addressSelect.val('0');
// Clear the search element when entering a manual address.
// This is to pass validation.
// searchElement.val('');
// Remove the error element when making a manual address.
addressError.remove();
}
Expand Down Expand Up @@ -174,7 +167,6 @@
central_hub_webform_address_container.find('input.js-localgov-forms-webform-uk-address--' + value).val(addressSelected[value]);
});

// hideManualAddress(centralHubElement, 'soft');
showManualAddress(centralHubElement);
} else if ($(this).val() == 0) {
// If choosing the empty option, clear out the address fields.
Expand Down Expand Up @@ -211,9 +203,6 @@
hideManualAddress(centralHubElement, 'soft', settings);
}

// centralHubElement.find('.js-address-searchstring').change(function() {
// hideManualAddress(centralHubElement, 'hard');
// });
centralHubElement.find('.js-reset-address').click(function () {
hideManualAddress(centralHubElement, 'hard', settings);
});
Expand Down
5 changes: 5 additions & 0 deletions localgov_forms.services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@ services:
localgov_forms.address_lookup:
class: Drupal\localgov_forms\AddressLookup
arguments: ['@geocoder', '@localgov_forms.geocoder_selection']

# Plugin manager service for PII redaction from Webform submissions.
plugin.manager.pii_redactor:
class: Drupal\localgov_forms\Plugin\PIIRedactorPluginManager
parent: default_plugin_manager
108 changes: 108 additions & 0 deletions modules/localgov_forms_lts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
## Long term storage for Webform submissions
This module copies Webform submissions to a separate database for Long Term Storage (LTS). The LTS database can then be used for data warehousing needs such as reporting and analysis. Optionally, Personally Identifiable Information (PII) can be redacted from Webform submissions while they are copied to the LTS database.

### Setup process
- Create a database which will serve as the LTS database.
- Declare it in Drupal's settings.php using the **localgov_forms_lts** key. Example:
```
$databases['localgov_forms_lts']['default'] = [
'database' => 'our-long-term-storage-database',
'username' => 'database-username-goes-here',
'password' => 'database-password-goes-here',
'host' => 'database-hostname-goes-here',
'port' => '3306',
'driver' => 'mysql',
'prefix' => '',
];
```
- Install the localgov_forms_lts submodule.
- Check the module requirement report from Drupal's status page at `admin/reports/status`. This should be under the **LocalGov Forms LTS** key.
- [Optional] If all looks good in the previous step, run `drush localgov-forms-lts:copy --force` which will copy existing Webform submissions into the LTS database.
- By default, periodic Webform submissions copying to the LTS database is disabled. Activate it from `/admin/structure/webform/config/submissions-lts`.
- Ensure cron is running periodically. This will copy any new Webform submissions or changes to existing Webform submissions since the last cron run or the last `drush localgov-forms-lts:copy` run.
- [Optional] Tell individual Webforms to purge submissions older than a chosen period. This is configured for each Webform from its `Settings > Submissions > Submission purge settings` configuration section.

### Inspection
To inspect Webform submissions kept in Long term storage, look for the **LTS** tab in the Webform submissions listing page. This is usually at `/admin/structure/webform/submissions/manage`.

### Good to know
- Each cron run copies 50 Webform submissions. If your site is getting more than that many Webform submissions between subsequent cron runs, not all Webform submissions will get copied to LTS during a certain period. If that happens, adjust cron run frequency.
- Files attached to Webform submissions are *not* moved to LTS.
- You can choose to redact elements with Personally Identifiable Information (PII) while they are copied to the LTS database. For that, select *Best effort PII redactor* (or another redactor) from the `PII redactor plugin` dropdown in the LTS config page at `/admin/structure/webform/config/submissions-lts`. At the moment, this plugin redacts all name, email, telephone, number, and various address type elements. Additionally, any text or radio or checkbox element whose machine name (AKA Key) contains the following also gets redacted: name, mail, phone, contact_number, date_of_birth, dob_, personal_, title, nino, passport, postcode, address, serial_number, reg_number, pcn_, and driver_.
- If you are using this module in multiple instances of the same site (e.g. dev/stage/live), ensure that the database settings array points to *different* databases. Alternatively, disable copying for the non-live environments from `/admin/structure/webform/config/submissions-lts`. The relevant settings `localgov_forms_lts.settings:is_copying_enabled` can be [overridden](https://www.drupal.org/docs/drupal-apis/configuration-api/configuration-override-system#s-global-overrides) from settings.php. The [config_split](https://www.drupal.org/project/config_split) module can be handy as well.
- This module is currently in experimental stage.

### Todo
- Removal of Webform submissions from LTS after a predefined period e.g. 5 years.

### Testing in DDEV

To set up testing in ddev, we'll need to set up a second database.

There are a few ways to do this, but the following seems to work.

#### 1. Add a post-start hook to your .ddev/config.yml

Edit `.ddev/config.yml` and add the following to create a new database on start.

```
hooks:
post-start:
- exec: mysql -uroot -proot -e "CREATE DATABASE IF NOT EXISTS localgov_forms_lts; GRANT ALL ON localgov_forms_lts.* to 'db'@'%';"
service: db
```

#### 2. Add the database connection string to settings.php:

Edit sites/default/settings.php and add a new database connection string at the
end of the file.

```
// Database connection for localgov_forms_lts.
$databases['localgov_forms_lts']['default'] = [
'database' => 'localgov_forms_lts',
'username' => 'db',
'password' => 'db',
'host' => 'db',
'port' => '3306',
'driver' => 'mysql',
'prefix' => '',
];
```

#### 3. Install Adminer

Adminer is useful if you want to inspect databases and tables.

```
ddev get ddev/ddev-adminer
```

#### 4. Restart ddev

```
ddev restart
```

#### 5. Require and install the module.

```
ddev composer require localgovdrupal/localgov_forms
ddev drush si localgov_forms_lts -y
```

#### 5. Make some submissions.

For example, in LocalGov Drupal we tend to have a contact form at /form/contact.

Make a couple of submissions there.

#### 6. Run cron.

ddev drush cron

#### 7. Inspect the LTS tab

Go to /admin/structure/webform/submissions/lts

Here you should see your submissions with redacted name and email address.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
is_copying_enabled: false
pii_redactor_plugin_id: ''
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Schema for the configuration files of the localgov_forms_lts submodule.

localgov_forms_lts.settings:
type: config_object
label: 'Webform submissions LTS config'
mapping:
is_copying_enabled:
type: boolean
label: 'Is copying to LTS database enabled?'
pii_redactor_plugin_id:
type: machine_name
label: 'PII redactor plugin id'
10 changes: 10 additions & 0 deletions modules/localgov_forms_lts/localgov_forms_lts.info.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name: LocalGov Forms long term storage
type: module
description: Long term storage for Webform submissions.
core_version_requirement: ^10 || ^11
php: 8.0
package: LocalGov Drupal
lifecycle: experimental

dependencies:
- webform:webform
106 changes: 106 additions & 0 deletions modules/localgov_forms_lts/localgov_forms_lts.install
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?php

/**
* @file
* Install and update hook implementations.
*/

use Drupal\Core\Database\Database;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\localgov_forms_lts\Constants;
use Drupal\webform\WebformSubmissionStorageSchema;

/**
* Implements hook_install().
*
* Creates all entity storage tables associated with the webform_submission
* entity type.
*/
function localgov_forms_lts_install($is_syncing): void {

if (!localgov_forms_lts_has_db()) {
return;
}

$webform_submission_schema = _localgov_forms_lts_get_webform_submission_storage_schema();

foreach ($webform_submission_schema as $table_name => $table_schema) {
_localgov_forms_lts_copy_table($table_name, $table_schema, db: Constants::LTS_DB_KEY);
}
}

/**
* Implements hook_requirements().
*
* Checks for the presence of the localgov_forms_lts database.
*/
function localgov_forms_lts_requirements($phase) {

$requirements = [
'localgov_forms_lts' => [
'title' => t('LocalGov Forms LTS'),
'value' => t('Available'),
'description' => t('LocalGov Forms LTS database available.'),
'severity' => REQUIREMENT_OK,
],
];

if (!function_exists('localgov_forms_lts_has_db')) {
// Some necessary files have not been loaded yet during the "install" phase.
require_once __DIR__ . '/localgov_forms_lts.module';
require_once __DIR__ . '/src/Constants.php';
}

if (!localgov_forms_lts_has_db()) {
$requirements['localgov_forms_lts']['value'] = t('Unavailable');
$requirements['localgov_forms_lts']['description'] = t('The LocalGov Forms LTS database must exist for this module to function.');
$requirements['localgov_forms_lts']['severity'] = REQUIREMENT_ERROR;
}

return $requirements;
}

/**
* Extracts entity storage schema.
*
* Returns the entity storage schema for the webform_submission content entity.
*/
function _localgov_forms_lts_get_webform_submission_storage_schema(): array {

$entity_type_manager = Drupal::service('entity_type.manager');
$entity_storage = $entity_type_manager->getStorage('webform_submission');
$entity_type = $entity_storage->getEntityType();
$entity_field_manager = Drupal::service('entity_field.manager');
$db_service = Drupal::service('database');

// @phpstan-ignore-next-line Avoid incorrect type inference of $entity_type.
$entity_schema = (new class($entity_type_manager, $entity_type, $entity_storage, $db_service, $entity_field_manager) extends WebformSubmissionStorageSchema {

/**
* Public wrapper over protected method.
*/
public function getEntitySchemaWrapper(ContentEntityTypeInterface $entity_type) {

return parent::getEntitySchema($entity_type);
}

})->getEntitySchemaWrapper($entity_type);

return $entity_schema;
}

/**
* Creates database tables.
*/
function _localgov_forms_lts_copy_table(string $table_name, array $table_schema, string $db): void {

$db_connection = Database::getConnection(key: $db);
$tx = $db_connection->startTransaction();

try {
$db_connection->schema()->createTable($table_name, $table_schema);
}
catch (Exception $e) {
$tx->rollBack();
}
}
27 changes: 27 additions & 0 deletions modules/localgov_forms_lts/localgov_forms_lts.links.task.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Submissions > LTS
entity.webform_submission.lts_collection:
title: 'LTS'
route_name: entity.webform_submission.lts_collection
parent_id: entity.webform_submission.collection
weight: 21

# View
entity.webform_submission.lts_view:
title: 'View'
route_name: entity.webform_submission.lts_view
base_route: entity.webform_submission.lts_view
weight: 1

# Notes
entity.webform_submission.lts_notes:
title: 'Notes'
route_name: entity.webform_submission.lts_notes
base_route: entity.webform_submission.lts_view
weight: 3

# Config tab
localgov_forms_lts.lts_config:
title: 'LTS'
route_name: localgov_forms_lts.lts_config
parent_id: webform.config
weight: 43
Loading