commit 5312a73510c2fd50eadb2fa83e20e1d6270ca961
parent 60cffc9dcffb515d8f216ac8a92653f4c6bbccf0
Author: Christian Grothoff <christian@grothoff.org>
Date: Sat, 11 Oct 2025 16:44:35 +0200
refactor to de-duplicate code
Diffstat:
4 files changed, 311 insertions(+), 337 deletions(-)
diff --git a/src/Form/TurnstileSettingsForm.php b/src/Form/TurnstileSettingsForm.php
@@ -11,6 +11,7 @@ use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field\Entity\FieldConfig;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
+use Drupal\turnstile\TurnstileFieldManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@@ -26,6 +27,13 @@ class TurnstileSettingsForm extends ConfigFormBase {
protected $entityTypeManager;
/**
+ * The Turnstile field manager.
+ *
+ * @var \Drupal\turnstile\TurnstileFieldManager
+ */
+ protected $fieldManager;
+
+ /**
* Constructs a TurnstileSettingsForm object.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
@@ -33,9 +41,10 @@ class TurnstileSettingsForm extends ConfigFormBase {
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
- public function __construct(ConfigFactoryInterface $config_factory, EntityTypeManagerInterface $entity_type_manager) {
+ public function __construct(ConfigFactoryInterface $config_factory, EntityTypeManagerInterface $entity_type_manager, TurnstileFieldManager $field_manager) {
parent::__construct($config_factory);
$this->entityTypeManager = $entity_type_manager;
+ $this->fieldManager = $field_manager;
}
/**
@@ -44,7 +53,8 @@ class TurnstileSettingsForm extends ConfigFormBase {
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.factory'),
- $container->get('entity_type.manager')
+ $container->get('entity_type.manager'),
+ $container->get('turnstile.field_manager')
);
}
@@ -265,12 +275,12 @@ class TurnstileSettingsForm extends ConfigFormBase {
// Add fields to newly enabled content types.
if (!empty($types_to_add)) {
- $this->addFieldsToContentTypes($types_to_add);
+ $this->fieldManager->addFieldsToContentTypes($types_to_add);
}
// Remove fields from disabled content types.
if (!empty($types_to_remove)) {
- $this->removeFieldsFromContentTypes($types_to_remove);
+ $this->fieldManager->removeFieldsFromContentTypes($types_to_remove);
}
$grant_access_on_error = $form_state->getValue('grant_access_on_error');
@@ -291,240 +301,6 @@ class TurnstileSettingsForm extends ConfigFormBase {
}
/**
- * Add price fields to content types.
- *
- * @param array $bundles
- * Array of content type machine names.
- */
- protected function addFieldsToContentTypes(array $bundles) {
- // Ensure field storage exists.
- $field_price_storage = FieldStorageConfig::loadByName('node', 'field_price');
- if (!$field_price_storage) {
- $field_price_storage = FieldStorageConfig::create([
- 'field_name' => 'field_price',
- 'entity_type' => 'node',
- 'type' => 'string',
- 'cardinality' => 1, // FIXME: should we allow cardinality > 1 instead of ","-separation here?
- 'settings' => [
- 'max_length' => 255,
- 'case_sensitive' => FALSE,
- 'is_ascii' => TRUE,
- ],
- ]);
- $field_price_storage->save();
- }
-
- // FIXME: code duplication with turnstile.install!
- $field_category_storage = FieldStorageConfig::loadByName('node', 'field_turnstile_price_category');
- if (!$field_category_storage) {
- $field_category_storage = FieldStorageConfig::create([
- 'field_name' => 'field_turnstile_price_category',
- 'entity_type' => 'node',
- 'type' => 'entity_reference',
- 'module' => 'core', // FIXME: should this be system?
- 'cardinality' => 1,
- 'settings' => [
- 'target_type' => 'turnstile_price_category',
- ],
- ]);
- $field_category_storage->save();
- }
-
- foreach ($bundles as $bundle) {
- // Verify content type exists.
- if (!$this->entityTypeManager->getStorage('node_type')->load($bundle)) {
- continue;
- }
-
- // Check if field already exists for this bundle.
- $existing_field = FieldConfig::loadByName('node', $bundle, 'field_price');
- if ($existing_field) {
- // If field exists, still ensure constraint is applied as we added that logic later.
- $constraints = $existing_field->getConstraints();
- if (!isset($constraints['TalerPriceListFormat'])) {
- $constraints['TalerPriceListFormat'] = [];
- $existing_field->set('constraints', $constraints);
- $existing_field->save();
- }
- }
- else
- {
- // Create field configuration.
- $field_config = FieldConfig::create([
- 'field_storage' => $field_price_storage,
- 'bundle' => $bundle,
- 'label' => 'Price',
- 'description' => 'Price for accessing this content (e.g., "EUR:5" or "USD:5,CHF:3.50" to support payment in multiple currencies)',
- 'required' => FALSE,
- ]);
- // This adds the PriceFormatConstraintValidator to the field.
- $field_config->setConstraints(['TalerPriceListFormat' => []]);
- $field_config->save();
-
- // Add to form display.
- $form_display = EntityFormDisplay::load('node.' . $bundle . '.default');
- if ($form_display) {
- $form_display->setComponent('field_price', [
- 'type' => 'string_textfield',
- 'weight' => 10,
- 'settings' => [
- 'size' => 60,
- 'placeholder' => 'e.g., EUR:1 or USD:5,CHF:3.50',
- ],
- ]);
- $form_display->save();
- }
-
- // Add to view display.
- $view_display = EntityViewDisplay::load('node.' . $bundle . '.default');
- if ($view_display) {
- $view_display->setComponent('field_price', [
- 'type' => 'string',
- 'weight' => 10,
- 'label' => 'above',
- ]);
- $view_display->save();
- }
- } /* end field_price did not exist */
-
-
- // Check if field already exists for this bundle.
- $existing_field = FieldConfig::loadByName('node', $bundle, 'field_turnstile_price_category');
- if ($existing_field) {
- // FIXME: initialize constraints on field?
- }
- else
- {
- // Create field configuration.
- $field_config = FieldConfig::create([
- 'field_name' => 'field_turnstile_price_category',
- 'entity_type' => 'node',
- 'field_storage' => $field_category_storage,
- 'bundle' => $bundle,
- 'label' => t('Price Category'),
- 'description' => t('Select a price category for this content.'),
- 'required' => FALSE,
- 'settings' => [
- 'handler' => 'default:turnstile_price_category',
- 'handler_settings' => [
- 'target_bundles' => NULL,
- 'sort' => [
- 'field' => 'label',
- 'direction' => 'ASC',
- ],
- 'auto_create' => FALSE,
- ],
- ],
- ]);
- // FIXME: constraints?
- $field_config->save();
-
- // Add to form display.
- $form_display = EntityFormDisplay::load('node.' . $bundle . '.default');
- if ($form_display) {
- $form_display->setComponent('field_turnstile_price_category', [
- 'type' => 'options_select',
- 'weight' => 10,
- 'settings' => [],
- ]);
- $form_display->save();
- }
-
- // Add to view display.
- $view_display = EntityViewDisplay::load('node.' . $bundle . '.default');
- if ($view_display) {
- $view_display->setComponent('field_turnstile_price_category', [
- 'type' => 'entity_reference_label',
- 'weight' => 10,
- 'label' => 'above',
- 'settings' => [
- 'link' => FALSE,
- ],
- ]);
- $view_display->save();
- }
- } // end field_turnstile_price_category did not exist
-
- } // for each bundle
-
- }
-
- /**
- * Remove price fields from content types.
- *
- * @param array $bundles
- * Array of content type machine names.
- */
- protected function removeFieldsFromContentTypes(array $bundles) {
- foreach ($bundles as $bundle) {
- $field_config = FieldConfig::loadByName('node', $bundle, 'field_price');
- if ($field_config) {
- $field_config->delete();
- }
- $field_config = FieldConfig::loadByName('node', $bundle, 'field_turnstile_price_category');
- if ($field_config) {
- $field_config->delete();
- }
-
- // Remove from form display.
- $form_display = EntityFormDisplay::load('node.' . $bundle . '.default');
- if ($form_display) {
- $form_display->removeComponent('field_price');
- $form_display->removeComponent('field_turnstile_price_category');
- $form_display->save();
- }
-
- // Remove from view display.
- $view_display = EntityViewDisplay::load('node.' . $bundle . '.default');
- if ($view_display) {
- $view_display->removeComponent('field_price');
- $view_display->removeComponent('field_turnstile_price_category');
- $view_display->save();
- }
- }
-
- // Check if field storage should be deleted.
- $this->cleanupFieldStorage();
- }
-
- /**
- * Clean up field storage if no content types are using it.
- */
- protected function cleanupFieldStorage() {
- $field_price_storage = FieldStorageConfig::loadByName('node', 'field_price');
- if ($field_price_storage) {
-
- // Get all field configs that use this storage.
- $field_configs = $this->entityTypeManager
- ->getStorage('field_config')
- ->loadByProperties([
- 'field_storage' => $field_price_storage,
- ]);
-
- // If no field configs exist, delete the storage.
- if (empty($field_configs)) {
- $field_price_storage->delete();
- }
- }
-
- $field_category_storage = FieldStorageConfig::loadByName('node', 'field_turnstile_price_category');
- if ($field_category_storage) {
-
- // Get all field configs that use this storage.
- $field_configs = $this->entityTypeManager
- ->getStorage('field_config')
- ->loadByProperties([
- 'field_storage' => $field_category_storage,
- ]);
-
- // If no field configs exist, delete the storage.
- if (empty($field_configs)) {
- $field_category_storage->delete();
- }
- }
- }
-
- /**
* Display messages about field changes.
*
* @param array $types_added
diff --git a/src/TurnstileFieldManager.php b/src/TurnstileFieldManager.php
@@ -0,0 +1,282 @@
+<?php
+
+namespace Drupal\turnstile;
+
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\Core\Entity\Entity\EntityFormDisplay;
+use Drupal\Core\Entity\Entity\EntityViewDisplay;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Service for managing Turnstile fields on content types.
+ */
+class TurnstileFieldManager {
+
+ use StringTranslationTrait;
+
+ /**
+ * The entity type manager.
+ *
+ * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+ */
+ protected $entityTypeManager;
+
+ /**
+ * Constructs a TurnstileFieldManager object.
+ *
+ * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+ * The entity type manager.
+ */
+ public function __construct(EntityTypeManagerInterface $entity_type_manager) {
+ $this->entityTypeManager = $entity_type_manager;
+ }
+
+ /**
+ * Add Turnstile fields to specified content types.
+ *
+ * @param array $bundles
+ * Array of content type machine names.
+ */
+ public function addFieldsToContentTypes(array $bundles) {
+ // Ensure field storage exists.
+ $this->ensureFieldStorageExists();
+
+ foreach ($bundles as $bundle) {
+ // Verify content type exists.
+ if (!$this->entityTypeManager->getStorage('node_type')->load($bundle)) {
+ continue;
+ }
+
+ $this->addPriceField($bundle);
+ $this->addPriceCategoryField($bundle);
+ }
+ }
+
+ /**
+ * Ensure field storage configurations exist.
+ */
+ protected function ensureFieldStorageExists() {
+ // Create price field storage if it doesn't exist.
+ $field_price_storage = FieldStorageConfig::loadByName('node', 'field_price');
+ if (!$field_price_storage) {
+ $field_price_storage = FieldStorageConfig::create([
+ 'field_name' => 'field_price',
+ 'entity_type' => 'node',
+ 'type' => 'string',
+ 'cardinality' => 1,
+ 'settings' => [
+ 'max_length' => 255,
+ 'case_sensitive' => FALSE,
+ 'is_ascii' => TRUE,
+ ],
+ ]);
+ $field_price_storage->save();
+ }
+
+ // Create price category field storage if it doesn't exist.
+ $field_category_storage = FieldStorageConfig::loadByName('node', 'field_turnstile_price_category');
+ if (!$field_category_storage) {
+ $field_category_storage = FieldStorageConfig::create([
+ 'field_name' => 'field_turnstile_price_category',
+ 'entity_type' => 'node',
+ 'type' => 'entity_reference',
+ 'cardinality' => 1,
+ 'settings' => [
+ 'target_type' => 'turnstile_price_category',
+ ],
+ ]);
+ $field_category_storage->save();
+ }
+ }
+
+ /**
+ * Add price field to a bundle.
+ *
+ * @param string $bundle
+ * The bundle machine name.
+ */
+ protected function addPriceField($bundle) {
+ $field_price_storage = FieldStorageConfig::loadByName('node', 'field_price');
+
+ $existing_field = FieldConfig::loadByName('node', $bundle, 'field_price');
+ if ($existing_field) {
+ // Ensure constraint is applied.
+ $constraints = $existing_field->getConstraints();
+ if (!isset($constraints['TalerPriceListFormat'])) {
+ $constraints['TalerPriceListFormat'] = [];
+ $existing_field->set('constraints', $constraints);
+ $existing_field->save();
+ }
+ }
+ else {
+ // Create field configuration.
+ $field_config = FieldConfig::create([
+ 'field_storage' => $field_price_storage,
+ 'bundle' => $bundle,
+ 'label' => 'Price',
+ 'description' => 'Price for accessing this content (e.g., "EUR:5" or "USD:5,CHF:3.50" to support payment in multiple currencies)',
+ 'required' => FALSE,
+ ]);
+ $field_config->setConstraints(['TalerPriceListFormat' => []]);
+ $field_config->save();
+
+ // Add to form display.
+ $form_display = EntityFormDisplay::load('node.' . $bundle . '.default');
+ if ($form_display) {
+ $form_display->setComponent('field_price', [
+ 'type' => 'string_textfield',
+ 'weight' => 10,
+ 'settings' => [
+ 'size' => 60,
+ 'placeholder' => 'e.g., EUR:1 or USD:5,CHF:3.50',
+ ],
+ ]);
+ $form_display->save();
+ }
+
+ // Add to view display.
+ $view_display = EntityViewDisplay::load('node.' . $bundle . '.default');
+ if ($view_display) {
+ $view_display->setComponent('field_price', [
+ 'type' => 'string',
+ 'weight' => 10,
+ 'label' => 'above',
+ ]);
+ $view_display->save();
+ }
+ }
+ }
+
+ /**
+ * Add price category field to a bundle.
+ *
+ * @param string $bundle
+ * The bundle machine name.
+ */
+ protected function addPriceCategoryField($bundle) {
+ $field_category_storage = FieldStorageConfig::loadByName('node', 'field_turnstile_price_category');
+
+ $existing_field = FieldConfig::loadByName('node', $bundle, 'field_turnstile_price_category');
+ if (!$existing_field) {
+ // Create field configuration.
+ $field_config = FieldConfig::create([
+ 'field_name' => 'field_turnstile_price_category',
+ 'entity_type' => 'node',
+ 'field_storage' => $field_category_storage,
+ 'bundle' => $bundle,
+ 'label' => $this->t('Price Category'),
+ 'description' => $this->t('Select a price category for this content.'),
+ 'required' => FALSE,
+ 'settings' => [
+ 'handler' => 'default:turnstile_price_category',
+ 'handler_settings' => [
+ 'target_bundles' => NULL,
+ 'sort' => [
+ 'field' => 'label',
+ 'direction' => 'ASC',
+ ],
+ 'auto_create' => FALSE,
+ ],
+ ],
+ ]);
+ $field_config->save();
+
+ // Add to form display.
+ $form_display = EntityFormDisplay::load('node.' . $bundle . '.default');
+ if ($form_display) {
+ $form_display->setComponent('field_turnstile_price_category', [
+ 'type' => 'options_select',
+ 'weight' => 10,
+ 'settings' => [],
+ ]);
+ $form_display->save();
+ }
+
+ // Add to view display.
+ $view_display = EntityViewDisplay::load('node.' . $bundle . '.default');
+ if ($view_display) {
+ $view_display->setComponent('field_turnstile_price_category', [
+ 'type' => 'entity_reference_label',
+ 'weight' => 10,
+ 'label' => 'above',
+ 'settings' => [
+ 'link' => FALSE,
+ ],
+ ]);
+ $view_display->save();
+ }
+ }
+ }
+
+ /**
+ * Remove Turnstile fields from specified content types.
+ *
+ * @param array $bundles
+ * Array of content type machine names.
+ */
+ public function removeFieldsFromContentTypes(array $bundles) {
+ foreach ($bundles as $bundle) {
+ $field_config = FieldConfig::loadByName('node', $bundle, 'field_price');
+ if ($field_config) {
+ $field_config->delete();
+ }
+
+ $field_config = FieldConfig::loadByName('node', $bundle, 'field_turnstile_price_category');
+ if ($field_config) {
+ $field_config->delete();
+ }
+
+ // Remove from form display.
+ $form_display = EntityFormDisplay::load('node.' . $bundle . '.default');
+ if ($form_display) {
+ $form_display->removeComponent('field_price');
+ $form_display->removeComponent('field_turnstile_price_category');
+ $form_display->save();
+ }
+
+ // Remove from view display.
+ $view_display = EntityViewDisplay::load('node.' . $bundle . '.default');
+ if ($view_display) {
+ $view_display->removeComponent('field_price');
+ $view_display->removeComponent('field_turnstile_price_category');
+ $view_display->save();
+ }
+ }
+
+ $this->cleanupFieldStorage();
+ }
+
+ /**
+ * Clean up field storage if no content types are using it.
+ */
+ protected function cleanupFieldStorage() {
+ $field_price_storage = FieldStorageConfig::loadByName('node', 'field_price');
+ if ($field_price_storage) {
+ $field_configs = $this->entityTypeManager
+ ->getStorage('field_config')
+ ->loadByProperties([
+ 'field_storage' => $field_price_storage,
+ ]);
+
+ if (empty($field_configs)) {
+ $field_price_storage->delete();
+ }
+ }
+
+ $field_category_storage = FieldStorageConfig::loadByName('node', 'field_turnstile_price_category');
+ if ($field_category_storage) {
+ $field_configs = $this->entityTypeManager
+ ->getStorage('field_config')
+ ->loadByProperties([
+ 'field_storage' => $field_category_storage,
+ ]);
+
+ if (empty($field_configs)) {
+ $field_category_storage->delete();
+ }
+ }
+ }
+
+}
+\ No newline at end of file
diff --git a/turnstile.install b/turnstile.install
@@ -14,114 +14,25 @@ use Drupal\Core\Entity\Entity\EntityViewDisplay;
* Implements hook_install().
*/
function turnstile_install() {
- // Create the price category field storage.
- $field_storage = FieldStorageConfig::create([
- 'field_name' => 'field_turnstile_price_category',
- 'entity_type' => 'node',
- 'type' => 'string', // FIXME: bad type...
- 'module' => 'core', // FIXME: should this be system?
- 'cardinality' => 1,
- 'settings' => [
- 'max_length' => 255,
- ],
- ]);
- $field_storage->save();
-
- // FIXME: code duplication with TurnstileSettingsForm.php!
- // Create the price field storage.
- $field_storage = FieldStorageConfig::create([
- 'field_name' => 'field_price',
- 'entity_type' => 'node',
- 'type' => 'string',
- 'cardinality' => 1,
- 'settings' => [
- 'max_length' => 255,
- ],
- ]);
- $field_storage->save();
-
- // Get enabled content types from config or default to 'article'.
$config = \Drupal::config('turnstile.settings');
$enabled_types = $config->get('enabled_content_types') ?: ['article'];
- // Add the price field to enabled content types.
- foreach ($enabled_types as $bundle) {
- if (\Drupal::entityTypeManager()->getStorage('node_type')->load($bundle)) {
- $field_config = FieldConfig::create([
- 'field_storage' => $field_storage,
- 'bundle' => $bundle,
- 'label' => 'Price',
- 'description' => 'Price for accessing this content',
- 'required' => FALSE,
- ]);
- $field_config->save();
-
- $field_config = FieldConfig::create([
- 'field_name' => 'field_turnstile_price_category',
- 'entity_type' => 'node',
- 'bundle' => $bundle,
- 'label' => t('Price Category'),
- 'description' => t('Select a price category for this content.'),
- 'required' => FALSE,
- 'settings' => [
- 'handler' => 'default',
- 'handler_settings' => [
- 'target_bundles' => NULL,
- 'sort' => [
- 'field' => 'label',
- 'direction' => 'asc',
- ],
- 'auto_create' => FALSE,
- ],
- ],
- ]);
- $field_config->save();
-
- // Add to form display.
- $form_display = EntityFormDisplay::load('node.' . $bundle . '.default');
- if ($form_display) {
- $form_display->setComponent('field_price', [
- 'type' => 'string_textfield',
- 'weight' => 10,
- ]);
- $form_display->setComponent('field_turnstile_price_category', [
- 'type' => 'string_textfield', // FIXME: wrong type!
- 'weight' => 10,
- ]);
- $form_display->save();
- }
-
- // Add to view display.
- $view_display = EntityViewDisplay::load('node.' . $bundle . '.default');
- if ($view_display) {
- $view_display->setComponent('field_price', [
- 'type' => 'string',
- 'weight' => 10,
- 'label' => 'above',
- ]);
- $view_display->setComponent('field_turnstile_price_category', [
- 'type' => 'entity_reference',
- 'weight' => 10,
- 'label' => 'above',
- ]);
- $view_display->save();
- }
- }
- }
+ /** @var TurnstileFieldManager $field_manager */
+ $field_manager = \Drupal::service('turnstile.field_manager');
+ $field_manager->addFieldsToContentTypes($enabled_types);
}
/**
* Implements hook_uninstall().
*/
function turnstile_uninstall() {
- // Remove field configurations.
- $field_storage = FieldStorageConfig::loadByName('node', 'field_turnstile_price_category');
- if ($field_storage) {
- $field_storage->delete();
- }
- $field_storage = FieldStorageConfig::loadByName('node', 'field_price');
- if ($field_storage) {
- $field_storage->delete();
+ /** @var TurnstileFieldManager $field_manager */
+ $field_manager = \Drupal::service('turnstile.field_manager');
+
+ $config = \Drupal::config('turnstile.settings');
+ $enabled_types = $config->get('enabled_content_types') ?: [];
+ if (!empty($enabled_types)) {
+ $field_manager->removeFieldsFromContentTypes($enabled_types);
}
// Clean up configuration.
diff --git a/turnstile.services.yml b/turnstile.services.yml
@@ -7,6 +7,10 @@ services:
class: Drupal\turnstile\TalerMerchantApiService
arguments: ['@http_client_factory', '@logger.channel.turnstile']
+ turnstile.field_manager:
+ class: Drupal\turnstile\TurnstileFieldManager
+ arguments: ['@entity_type.manager']
+
logger.channel.turnstile:
parent: logger.channel_base
arguments: ['turnstile']