From 8d1c7d6e7b19fc1a1ea7d064310e611c39302318 Mon Sep 17 00:00:00 2001 From: Vincent Gao Date: Wed, 11 Sep 2024 13:45:02 +1000 Subject: [PATCH] test --- ....taxonomy_term.captcha_widgets.default.yml | 64 +++++++ ....taxonomy_term.captcha_widgets.default.yml | 39 ++++ ...erm.captcha_widgets.field_captcha_type.yml | 20 ++ ...my_term.captcha_widgets.field_site_key.yml | 18 ++ ...orage.taxonomy_term.field_captcha_type.yml | 29 +++ ...d.storage.taxonomy_term.field_site_key.yml | 20 ++ .../taxonomy.vocabulary.captcha_widgets.yml | 7 + tide_webform.install | 27 +++ tide_webform.module | 174 ++++++++++++++++++ 9 files changed, 398 insertions(+) create mode 100644 config/optional/core.entity_form_display.taxonomy_term.captcha_widgets.default.yml create mode 100644 config/optional/core.entity_view_display.taxonomy_term.captcha_widgets.default.yml create mode 100644 config/optional/field.field.taxonomy_term.captcha_widgets.field_captcha_type.yml create mode 100644 config/optional/field.field.taxonomy_term.captcha_widgets.field_site_key.yml create mode 100644 config/optional/field.storage.taxonomy_term.field_captcha_type.yml create mode 100644 config/optional/field.storage.taxonomy_term.field_site_key.yml create mode 100644 config/optional/taxonomy.vocabulary.captcha_widgets.yml diff --git a/config/optional/core.entity_form_display.taxonomy_term.captcha_widgets.default.yml b/config/optional/core.entity_form_display.taxonomy_term.captcha_widgets.default.yml new file mode 100644 index 0000000..889e12b --- /dev/null +++ b/config/optional/core.entity_form_display.taxonomy_term.captcha_widgets.default.yml @@ -0,0 +1,64 @@ +langcode: en +status: true +dependencies: + config: + - field.field.taxonomy_term.captcha_widgets.field_captcha_type + - field.field.taxonomy_term.captcha_widgets.field_site_key + - taxonomy.vocabulary.captcha_widgets + module: + - path + - text +id: taxonomy_term.captcha_widgets.default +targetEntityType: taxonomy_term +bundle: captcha_widgets +mode: default +content: + description: + type: text_textarea + weight: 4 + region: content + settings: + rows: 5 + placeholder: '' + third_party_settings: { } + field_captcha_type: + type: options_select + weight: 1 + region: content + settings: { } + third_party_settings: { } + field_site_key: + type: string_textfield + weight: 3 + region: content + settings: + size: 60 + placeholder: '' + third_party_settings: { } + name: + type: string_textfield + weight: 0 + region: content + settings: + size: 60 + placeholder: '' + third_party_settings: { } + path: + type: path + weight: 5 + region: content + settings: { } + third_party_settings: { } + simple_sitemap: + weight: 10 + region: content + settings: { } + third_party_settings: { } + status: + type: boolean_checkbox + weight: 6 + region: content + settings: + display_label: true + third_party_settings: { } +hidden: { } diff --git a/config/optional/core.entity_view_display.taxonomy_term.captcha_widgets.default.yml b/config/optional/core.entity_view_display.taxonomy_term.captcha_widgets.default.yml new file mode 100644 index 0000000..e9cd673 --- /dev/null +++ b/config/optional/core.entity_view_display.taxonomy_term.captcha_widgets.default.yml @@ -0,0 +1,39 @@ +langcode: en +status: true +dependencies: + config: + - field.field.taxonomy_term.captcha_widgets.field_captcha_type + - field.field.taxonomy_term.captcha_widgets.field_site_key + - taxonomy.vocabulary.captcha_widgets + module: + - options + - text +id: taxonomy_term.captcha_widgets.default +targetEntityType: taxonomy_term +bundle: captcha_widgets +mode: default +content: + description: + type: text_default + label: hidden + settings: { } + third_party_settings: { } + weight: 0 + region: content + field_captcha_type: + type: list_default + label: above + settings: { } + third_party_settings: { } + weight: 2 + region: content + field_site_key: + type: string + label: above + settings: + link_to_entity: false + third_party_settings: { } + weight: 1 + region: content +hidden: + search_api_excerpt: true diff --git a/config/optional/field.field.taxonomy_term.captcha_widgets.field_captcha_type.yml b/config/optional/field.field.taxonomy_term.captcha_widgets.field_captcha_type.yml new file mode 100644 index 0000000..11aec33 --- /dev/null +++ b/config/optional/field.field.taxonomy_term.captcha_widgets.field_captcha_type.yml @@ -0,0 +1,20 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.taxonomy_term.field_captcha_type + - taxonomy.vocabulary.captcha_widgets + module: + - options +id: taxonomy_term.captcha_widgets.field_captcha_type +field_name: field_captcha_type +entity_type: taxonomy_term +bundle: captcha_widgets +label: 'Captcha type' +description: '' +required: true +translatable: false +default_value: { } +default_value_callback: '' +settings: { } +field_type: list_string diff --git a/config/optional/field.field.taxonomy_term.captcha_widgets.field_site_key.yml b/config/optional/field.field.taxonomy_term.captcha_widgets.field_site_key.yml new file mode 100644 index 0000000..efa2780 --- /dev/null +++ b/config/optional/field.field.taxonomy_term.captcha_widgets.field_site_key.yml @@ -0,0 +1,18 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.taxonomy_term.field_site_key + - taxonomy.vocabulary.captcha_widgets +id: taxonomy_term.captcha_widgets.field_site_key +field_name: field_site_key +entity_type: taxonomy_term +bundle: captcha_widgets +label: 'Site key' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: { } +field_type: string diff --git a/config/optional/field.storage.taxonomy_term.field_captcha_type.yml b/config/optional/field.storage.taxonomy_term.field_captcha_type.yml new file mode 100644 index 0000000..a178c31 --- /dev/null +++ b/config/optional/field.storage.taxonomy_term.field_captcha_type.yml @@ -0,0 +1,29 @@ +langcode: en +status: true +dependencies: + module: + - options + - taxonomy +id: taxonomy_term.field_captcha_type +field_name: field_captcha_type +entity_type: taxonomy_term +type: list_string +settings: + allowed_values: + - + value: google_recaptcha_v3 + label: 'Google reCAPTCHA v3' + - + value: google_recaptcha_v2 + label: 'Google reCAPTCHA v2' + - + value: cloudfare_turnstile + label: 'Cloudfare Turnstile' + allowed_values_function: '' +module: options +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/config/optional/field.storage.taxonomy_term.field_site_key.yml b/config/optional/field.storage.taxonomy_term.field_site_key.yml new file mode 100644 index 0000000..ed26eed --- /dev/null +++ b/config/optional/field.storage.taxonomy_term.field_site_key.yml @@ -0,0 +1,20 @@ +langcode: en +status: true +dependencies: + module: + - taxonomy +id: taxonomy_term.field_site_key +field_name: field_site_key +entity_type: taxonomy_term +type: string +settings: + max_length: 255 + case_sensitive: false + is_ascii: false +module: core +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/config/optional/taxonomy.vocabulary.captcha_widgets.yml b/config/optional/taxonomy.vocabulary.captcha_widgets.yml new file mode 100644 index 0000000..4891d46 --- /dev/null +++ b/config/optional/taxonomy.vocabulary.captcha_widgets.yml @@ -0,0 +1,7 @@ +langcode: en +status: true +dependencies: { } +name: 'CAPTCHA widgets' +vid: captcha_widgets +description: '' +weight: 0 diff --git a/tide_webform.install b/tide_webform.install index 61b09c5..6e48676 100644 --- a/tide_webform.install +++ b/tide_webform.install @@ -14,3 +14,30 @@ function tide_webform_install() { TideOperation::installWebformConfig(); TideOperation::accessTextEditorInWebform(); } + +/** + * Import configs for Captcha feature. + */ +function tide_webform_update_10001() { + $configs = [ + 'taxonomy.vocabulary.captcha_widgets' => 'taxonomy_vocabulary', + 'field.storage.taxonomy_term.field_site_key' => 'field_storage_config', + 'field.storage.taxonomy_term.field_captcha_type' => 'field_storage_config', + 'field.field.taxonomy_term.captcha_widgets.field_site_key' => 'field_config', + 'field.field.taxonomy_term.captcha_widgets.field_captcha_type' => 'field_config', + 'core.entity_view_display.taxonomy_term.captcha_widgets.default' => 'entity_view_display', + 'core.entity_form_display.taxonomy_term.captcha_widgets.default' => 'entity_form_display', + ]; + + \Drupal::moduleHandler()->loadInclude('tide_core', 'inc', 'includes/helpers'); + $config_location = [\Drupal::service('extension.list.module')->getPath('tide_webform') . '/config/optional']; + foreach ($configs as $config_name => $type) { + $config_read = _tide_read_config($config_name, $config_location, TRUE); + $storage = \Drupal::entityTypeManager()->getStorage($type); + $id = $storage->getIDFromConfigName($config_name, $storage->getEntityType()->getConfigPrefix()); + if ($storage->load($id) == NULL) { + $config_entity = $storage->createFromStorageRecord($config_read); + $config_entity->save(); + } + } +} diff --git a/tide_webform.module b/tide_webform.module index ab61b20..e4e37ad 100644 --- a/tide_webform.module +++ b/tide_webform.module @@ -15,6 +15,7 @@ use Drupal\Core\Session\AccountInterface; use Drupal\webform\Entity\Webform; use Drupal\webform\Entity\WebformSubmission; use Drupal\webform\WebformSubmissionForm; +use Drupal\taxonomy\Entity\Term; /** * Implements hook_config_ignore_settings_alter(). @@ -438,3 +439,176 @@ function tide_webform_webform_element_default_properties_alter(array &$propertie function tide_webform_webform_update(EntityInterface $entity) { \Drupal::cache()->invalidate('webform_text_fields_default_maxlength'); } + +/** + * Implements hook_webform_third_party_settings_form_alter(). + */ +function tide_webform_webform_third_party_settings_form_alter(array &$form, FormStateInterface $form_state) { + $webform = $form_state->getFormObject()->getEntity(); + $third_party_settings = $webform->getThirdPartySettings('tide_webform_captcha'); + $user_input = $form_state->getUserInput(); + $captcha_type = NULL; + $allowed_values = []; + try { + $field_definition = \Drupal::entityTypeManager()->getStorage('field_storage_config')->load('taxonomy_term.field_captcha_type'); + if ($field_definition) { + $allowed_values = $field_definition->getSetting('allowed_values'); + } + else { + throw new \Exception('Field storage configuration not found for taxonomy_term.field_captcha_type'); + } + } + catch (\Exception $e) { + \Drupal::messenger()->addWarning('Unable to load CAPTCHA type options. Please contact the site administrator.'); + \Drupal::logger('tide_webform')->error('Error loading CAPTCHA type options: @message', ['@message' => $e->getMessage()]); + return; + } + if (isset($third_party_settings['captcha_type'])) { + $captcha_type = $third_party_settings['captcha_type']; + } + if (isset($user_input['third_party_settings']['tide_webform_captcha']['captcha_type'])) { + $captcha_type = $user_input['third_party_settings']['tide_webform_captcha']['captcha_type']; + } + $query = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->getQuery() + ->accessCheck(TRUE) + ->condition('vid', 'captcha_widgets') + ->condition('field_captcha_type', $captcha_type); + $tids = $query->execute(); + $terms = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadMultiple($tids); + $options = array_map(function (Term $term) { + return $term->label() . ' (' . $term->id() . ')'; + }, $terms); + + $form['third_party_settings']['tide_webform_captcha'] = [ + '#type' => 'fieldset', + '#title' => t('Tide webform CAPTCHA'), + '#open' => TRUE, + ]; + + $form['third_party_settings']['tide_webform_captcha']['enable_captcha'] = [ + '#type' => 'checkbox', + '#default_value' => !empty($third_party_settings['enable_captcha']) ? $third_party_settings['enable_captcha'] : NULL, + '#title' => ('Enable captcha'), + ]; + + $form['third_party_settings']['tide_webform_captcha']['captcha_type'] = [ + '#type' => 'select', + '#title' => 'Captcha type', + '#options' => [ + 'all' => '-Select-', + ] + $allowed_values, + '#default_value' => !empty($third_party_settings['captcha_type']) ? $third_party_settings['captcha_type'] : NULL, + '#ajax' => [ + 'callback' => '_tide_webform_captcha_type_dropdown_callback', + 'wrapper' => 'captcha-type-dropdown-container', + ], + ]; + $score_threshold = NULL; + if (isset($third_party_settings['score_threshold']) && $third_party_settings['score_threshold'] === 0.0) { + $score_threshold = '0.0'; + } elseif (!empty($third_party_settings['score_threshold'])) { + $score_threshold = (string)$third_party_settings['score_threshold']; + } + + $form['third_party_settings']['tide_webform_captcha']['score_threshold'] = [ + '#type' => 'textfield', + '#title' => t('Score threshold (reCAPTCHA v3)'), + '#size' => 2, + '#maxlength' => 3, + '#number_type' => 'decimal', + '#element_validate' => ['_tide_webform_threshold_validate'], + '#states' => [ + 'visible' => [ + ':input[name="third_party_settings[tide_webform_captcha][captcha_type]"]' => ['value' => 'google_recaptcha_v3'], + ], + ], + '#default_value' => $score_threshold, + '#description' => 'Enter a value between 0.0 and 1.0. Use only one decimal place (e.g., 0.0, 0.5, 1.0).', + ]; + + $form['third_party_settings']['tide_webform_captcha']['captcha_type_dropdown_container'] = [ + '#type' => 'fieldset', + '#attributes' => ['id' => 'captcha-type-dropdown-container'], + ]; + + $form['third_party_settings']['tide_webform_captcha']['captcha_type_dropdown_container']['captcha_details'] = [ + '#type' => 'select2', + '#title' => ('Site key'), + '#target_type' => 'taxonomy_term', + '#options' => $options, + '#tags' => TRUE, + '#select2' => [ + 'allowClear' => TRUE, + 'dropdownAutoWidth' => FALSE, + 'width' => '20%', + 'closeOnSelect' => FALSE, + ], + '#default_value' => !empty($third_party_settings['captcha_details']['term_id']) ? $third_party_settings['captcha_details']['term_id'] : NULL, + '#selection_settings' => [ + 'target_bundles' => ['captcha_widgets'], + ], + ]; + + $form['#validate'][] = '_tide_webform_captcha_form_validate'; +} + +/** + * Captcha form validate. + */ +function _tide_webform_captcha_form_validate(&$form, FormStateInterface $form_state) { + $third_party_settings = $form_state->getValue('third_party_settings'); + $tide_webform_settings = &$third_party_settings['tide_webform_captcha']; + + // Handle CAPTCHA details. + if (isset($tide_webform_settings['captcha_type_dropdown_container']['captcha_details'][0]['target_id'])) { + $term_id = $tide_webform_settings['captcha_type_dropdown_container']['captcha_details'][0]['target_id']; + if ($term_id) { + $term = Term::load($term_id); + $tide_webform_settings['captcha_details'] = [ + 'site_key' => $term->get('field_site_key')->value, + 'term_id' => $term_id, + ]; + } + } + elseif ($tide_webform_settings['captcha_type_dropdown_container']['captcha_details'] === NULL) { + $tide_webform_settings['captcha_details'] = NULL; + } + + // Remove the captcha_type_dropdown_container. + unset($tide_webform_settings['captcha_type_dropdown_container']); + + // Update the form state. + $form_state->setValue('third_party_settings', $third_party_settings); +} + +/** + * Captcha options callback. + */ +function _tide_webform_captcha_type_dropdown_callback($form, FormStateInterface $form_state) { + return $form['third_party_settings']['tide_webform_captcha']['captcha_type_dropdown_container']; +} + +/** + * Threshold element validation. + */ +function _tide_webform_threshold_validate($element, FormStateInterface $form_state) { + $number = $form_state->getUserInput()['third_party_settings']['tide_webform_captcha']['score_threshold']; + if ($number === 0 || $number === '' || $number === NULL) { + $form_state->setValue([ + 'third_party_settings', + 'tide_webform_captcha', + 'score_threshold', + ], NULL); + return; + } + if (preg_match('/^(0(\.[0-9])?|1(\.0)?)$/', (string) $number)) { + $form_state->setValue([ + 'third_party_settings', + 'tide_webform_captcha', + 'score_threshold', + ], (float)$number); + } + else { + $form_state->setError($element, t('Enter a value between 0.0 and 1.0. Use only one decimal place (e.g., 0.0, 0.5, 1.0).')); + } +}