From 50ddd7ff211a231f0e34ceb6a868cba797a0d6c2 Mon Sep 17 00:00:00 2001 From: Nigel Banks Date: Mon, 9 May 2022 05:26:08 -0200 Subject: [PATCH 1/6] Added third party widget settings to file widgets This allows for the validation of uploaded files against user provided checksums. --- dgi_fixity.info.yml | 1 + dgi_fixity.module | 105 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/dgi_fixity.info.yml b/dgi_fixity.info.yml index 6a6dbde..35817b5 100644 --- a/dgi_fixity.info.yml +++ b/dgi_fixity.info.yml @@ -9,4 +9,5 @@ dependencies: - drupal:media - drupal:user - drupal:views + - drupal:field_ui - filehash:filehash diff --git a/dgi_fixity.module b/dgi_fixity.module index 289a5e0..b70c216 100644 --- a/dgi_fixity.module +++ b/dgi_fixity.module @@ -10,10 +10,15 @@ use Drupal\Core\Config\FileStorage; use Drupal\Core\Config\InstallStorage; use Drupal\Core\Config\StorageInterface; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Field\FieldDefinitionInterface; +use Drupal\Core\Field\WidgetInterface; +use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Mail\MailFormatHelper; +use Drupal\Core\Render\Element; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Url; use Drupal\dgi_fixity\Form\SettingsForm; +use Drupal\file\Plugin\Field\FieldWidget\FileWidget; use Drupal\user\Entity\User; /** @@ -231,3 +236,103 @@ function dgi_fixity_help($route_name, RouteMatchInterface $route_match) { return $output; } } + +/** + * Implements hook_field_widget_third_party_settings_form(). + */ +function dgi_fixity_field_widget_third_party_settings_form(WidgetInterface $plugin, FieldDefinitionInterface $field_definition, $form_mode, $form, FormStateInterface $form_state) { + $element = []; + if ($plugin instanceof FileWidget) { + $field_name = $field_definition->getName(); + $element['validate'] = [ + '#type' => 'checkbox', + '#title' => \t('Show Validate Upload Elements'), + '#description' => \t('Displays a field for each enabled filehash algorithm, allowing the user to validate the uploaded file(s).'), + '#default_value' => $plugin->getThirdPartySetting('dgi_fixity', 'validate', FALSE), + ]; + $element['validate_require'] = [ + '#type' => 'checkbox', + '#title' => \t('Require Checksums'), + '#description' => \t('User is prevented from submitting the form unless all enabled filehash algorithms match the user provided values.'), + '#default_value' => $plugin->getThirdPartySetting('dgi_fixity', 'validate_require', FALSE), + '#states' => [ + 'visible' => [ + ":input[name=\"fields[${field_name}][settings_edit_form][third_party_settings][dgi_fixity][validate]\"]" => ['checked' => TRUE], + ], + ], + ]; + } + return $element; +} + +/** + * Implements hook_field_widget_single_element_form_alter(). + */ +function dgi_fixity_field_widget_single_element_form_alter(&$element, FormStateInterface $form_state, $context) { + // Set a message if this is for the form displayed to set default value for + // the field. + $widget = $context['widget'] ?? NULL; + if ($widget instanceof FileWidget) { + $settings = $widget->getThirdPartySettings('dgi_fixity'); + if ($settings['validate'] ?? FALSE) { + /** @var \Drupal\filehash\FileHashInterface $filehash */ + $filehash = \Drupal::service('filehash'); + $labels = $filehash->labels(); + $descriptions = $filehash->descriptions(); + + $element['#process'][] = '_dgi_fixity_file_widget_process'; + $element['#element_validate'][] = '_dgi_fixity_file_widget_validate'; + $element['algorithms'] = [ + '#title' => \t('Validate Upload'), + '#type' => 'details', + '#weight' => 100, + ]; + foreach ($filehash->columns() as $column) { + $element['algorithms'][$column] = [ + '#type' => 'textfield', + '#title' => $labels[$column], + '#description' => $descriptions[$column], + '#column' => $column, + '#required' => $settings['validate_require'] ?? FALSE, + ]; + } + } + } +} + +/** + * Sets default values for checksums if none provided. + * + * Done in the process step as the FileWidget process step is responsible for + * loading the file entity from which the default is derived. + */ +function _dgi_fixity_file_widget_process(&$element, FormStateInterface $form_state, &$complete_form) { + $file = reset($element['#files']) ?? NULL; + $element['algorithms']['#access'] = (bool) $element['#value']['fids']; + foreach (Element::children($element['algorithms']) as $column) { + $default_value = $element['#value']['algorithms'][$column] ?? NULL; + $default_value = $default_value ?? (isset($file->{$column}) ? $file->{$column}->value : NULL); + $element['algorithms'][$column]['#default_value'] = $default_value; + } + return $element; +} + +/** + * Validate user provided value against the value calculated by filehash. + */ +function _dgi_fixity_file_widget_validate($element, FormStateInterface $form_state) { + $file = reset($element['#files']) ?? NULL; + foreach (Element::children($element['algorithms']) as $column) { + $algorithm = &$element['algorithms'][$column]; + $provided = $algorithm['#value']; + $expected = $file->{$column}->value; + // If not required and no value given skip validation. + $ignore = !$algorithm['#required'] && empty($provided); + if (!$ignore && $provided !== $expected) { + $form_state->setError($algorithm, \t( + 'Provided value "@provided" did not match expected value "@expected".', + ['@provided' => $provided, '@expected' => $expected] + )); + } + } +} From 389a8c9b64ec995306f329ab79bd27a1a26ae0a9 Mon Sep 17 00:00:00 2001 From: Nigel Banks Date: Wed, 1 Jun 2022 10:02:12 +0100 Subject: [PATCH 2/6] Apply suggestions from code review Co-authored-by: Adam --- dgi_fixity.module | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dgi_fixity.module b/dgi_fixity.module index b70c216..fe44125 100644 --- a/dgi_fixity.module +++ b/dgi_fixity.module @@ -243,7 +243,6 @@ function dgi_fixity_help($route_name, RouteMatchInterface $route_match) { function dgi_fixity_field_widget_third_party_settings_form(WidgetInterface $plugin, FieldDefinitionInterface $field_definition, $form_mode, $form, FormStateInterface $form_state) { $element = []; if ($plugin instanceof FileWidget) { - $field_name = $field_definition->getName(); $element['validate'] = [ '#type' => 'checkbox', '#title' => \t('Show Validate Upload Elements'), @@ -257,7 +256,7 @@ function dgi_fixity_field_widget_third_party_settings_form(WidgetInterface $plug '#default_value' => $plugin->getThirdPartySetting('dgi_fixity', 'validate_require', FALSE), '#states' => [ 'visible' => [ - ":input[name=\"fields[${field_name}][settings_edit_form][third_party_settings][dgi_fixity][validate]\"]" => ['checked' => TRUE], + ":input[name=\"fields[{$field_definition->getName()}][settings_edit_form][third_party_settings][dgi_fixity][validate]\"]" => ['checked' => TRUE], ], ], ]; From 65b73dfcb8e01b2d0d8305629bfa4b6b2ae6fd71 Mon Sep 17 00:00:00 2001 From: Nigel Banks Date: Wed, 1 Jun 2022 10:30:08 +0100 Subject: [PATCH 3/6] Update dgi_fixity.module Co-authored-by: Adam --- dgi_fixity.module | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dgi_fixity.module b/dgi_fixity.module index fe44125..620c904 100644 --- a/dgi_fixity.module +++ b/dgi_fixity.module @@ -309,8 +309,7 @@ function _dgi_fixity_file_widget_process(&$element, FormStateInterface $form_sta $file = reset($element['#files']) ?? NULL; $element['algorithms']['#access'] = (bool) $element['#value']['fids']; foreach (Element::children($element['algorithms']) as $column) { - $default_value = $element['#value']['algorithms'][$column] ?? NULL; - $default_value = $default_value ?? (isset($file->{$column}) ? $file->{$column}->value : NULL); + $default_value = $element['#value']['algorithms'][$column] ?? $file->{$column}->value ?? NULL; $element['algorithms'][$column]['#default_value'] = $default_value; } return $element; From 01c83ab5b9b71b805d6ebe3dacd6e9b1141b2e83 Mon Sep 17 00:00:00 2001 From: Nigel Banks Date: Wed, 1 Jun 2022 06:42:22 -0300 Subject: [PATCH 4/6] Code Review feedback --- dgi_fixity.module | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/dgi_fixity.module b/dgi_fixity.module index 620c904..b1a435a 100644 --- a/dgi_fixity.module +++ b/dgi_fixity.module @@ -237,23 +237,31 @@ function dgi_fixity_help($route_name, RouteMatchInterface $route_match) { } } +function _dgi_fixity_file_widget_validate_settings(FileWidget $plugin) { + return [ + 'validate' => $plugin->getThirdPartySetting('dgi_fixity', 'validate', FALSE), + 'validate_require' => $plugin->getThirdPartySetting('dgi_fixity', 'validate_require', FALSE), + ]; +} + /** * Implements hook_field_widget_third_party_settings_form(). */ function dgi_fixity_field_widget_third_party_settings_form(WidgetInterface $plugin, FieldDefinitionInterface $field_definition, $form_mode, $form, FormStateInterface $form_state) { $element = []; if ($plugin instanceof FileWidget) { + $settings = _dgi_fixity_file_widget_validate_settings($plugin); $element['validate'] = [ '#type' => 'checkbox', '#title' => \t('Show Validate Upload Elements'), '#description' => \t('Displays a field for each enabled filehash algorithm, allowing the user to validate the uploaded file(s).'), - '#default_value' => $plugin->getThirdPartySetting('dgi_fixity', 'validate', FALSE), + '#default_value' => $settings['validate'], ]; $element['validate_require'] = [ '#type' => 'checkbox', '#title' => \t('Require Checksums'), '#description' => \t('User is prevented from submitting the form unless all enabled filehash algorithms match the user provided values.'), - '#default_value' => $plugin->getThirdPartySetting('dgi_fixity', 'validate_require', FALSE), + '#default_value' => $settings['validate_require'], '#states' => [ 'visible' => [ ":input[name=\"fields[{$field_definition->getName()}][settings_edit_form][third_party_settings][dgi_fixity][validate]\"]" => ['checked' => TRUE], @@ -270,10 +278,10 @@ function dgi_fixity_field_widget_third_party_settings_form(WidgetInterface $plug function dgi_fixity_field_widget_single_element_form_alter(&$element, FormStateInterface $form_state, $context) { // Set a message if this is for the form displayed to set default value for // the field. - $widget = $context['widget'] ?? NULL; - if ($widget instanceof FileWidget) { - $settings = $widget->getThirdPartySettings('dgi_fixity'); - if ($settings['validate'] ?? FALSE) { + $plugin = $context['widget'] ?? NULL; + if ($plugin instanceof FileWidget) { + $settings = _dgi_fixity_file_widget_validate_settings($plugin); + if ($settings['validate']) { /** @var \Drupal\filehash\FileHashInterface $filehash */ $filehash = \Drupal::service('filehash'); $labels = $filehash->labels(); @@ -292,7 +300,7 @@ function dgi_fixity_field_widget_single_element_form_alter(&$element, FormStateI '#title' => $labels[$column], '#description' => $descriptions[$column], '#column' => $column, - '#required' => $settings['validate_require'] ?? FALSE, + '#required' => $settings['validate_require'], ]; } } @@ -306,8 +314,8 @@ function dgi_fixity_field_widget_single_element_form_alter(&$element, FormStateI * loading the file entity from which the default is derived. */ function _dgi_fixity_file_widget_process(&$element, FormStateInterface $form_state, &$complete_form) { - $file = reset($element['#files']) ?? NULL; - $element['algorithms']['#access'] = (bool) $element['#value']['fids']; + $file = reset($element['#files']); + $element['algorithms']['#access'] = $file != FALSE; foreach (Element::children($element['algorithms']) as $column) { $default_value = $element['#value']['algorithms'][$column] ?? $file->{$column}->value ?? NULL; $element['algorithms'][$column]['#default_value'] = $default_value; @@ -319,7 +327,7 @@ function _dgi_fixity_file_widget_process(&$element, FormStateInterface $form_sta * Validate user provided value against the value calculated by filehash. */ function _dgi_fixity_file_widget_validate($element, FormStateInterface $form_state) { - $file = reset($element['#files']) ?? NULL; + $file = reset($element['#files']); foreach (Element::children($element['algorithms']) as $column) { $algorithm = &$element['algorithms'][$column]; $provided = $algorithm['#value']; From ff17de86dcf084cedca1a3064717d0f2828e4fc3 Mon Sep 17 00:00:00 2001 From: Nigel Banks Date: Wed, 1 Jun 2022 09:40:58 -0300 Subject: [PATCH 5/6] Appease phpcs --- dgi_fixity.module | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dgi_fixity.module b/dgi_fixity.module index b1a435a..dc45a6d 100644 --- a/dgi_fixity.module +++ b/dgi_fixity.module @@ -237,6 +237,9 @@ function dgi_fixity_help($route_name, RouteMatchInterface $route_match) { } } +/** + * Gets dgi_fixity file widget settings. + */ function _dgi_fixity_file_widget_validate_settings(FileWidget $plugin) { return [ 'validate' => $plugin->getThirdPartySetting('dgi_fixity', 'validate', FALSE), From 10073b101f4fd77bc701167a042f79720f0e8a86 Mon Sep 17 00:00:00 2001 From: Nigel Banks Date: Wed, 1 Jun 2022 10:14:41 -0300 Subject: [PATCH 6/6] Fixup schema to include third party configs. --- config/schema/dgi_fixity.schema.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/config/schema/dgi_fixity.schema.yml b/config/schema/dgi_fixity.schema.yml index 3530c2b..e5bc13c 100644 --- a/config/schema/dgi_fixity.schema.yml +++ b/config/schema/dgi_fixity.schema.yml @@ -23,3 +23,13 @@ dgi_fixity.settings: notify_user_threshold: type: string label: 'Time elapsed between notifications' + +field.widget.third_party.dgi_fixity: + type: mapping + mapping: + validate: + type: boolean + label: Show Validate Upload Elements + validate_require: + type: boolean + label: Require Checksums