turnstile

Drupal paywall plugin
Log | Files | Refs | README | LICENSE

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:
Msrc/Form/TurnstileSettingsForm.php | 252+++++--------------------------------------------------------------------------
Asrc/TurnstileFieldManager.php | 283+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mturnstile.install | 109++++++++-----------------------------------------------------------------------
Mturnstile.services.yml | 4++++
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']