commit 2b4f74897e3f112dc73d40d695c237f057c348bd
parent d92a965a5e3e7d755726b78e89e3ad7fbb1a0946
Author: Christian Grothoff <christian@grothoff.org>
Date: Mon, 20 Oct 2025 20:32:50 +0200
major rename fest
Diffstat:
33 files changed, 745 insertions(+), 747 deletions(-)
diff --git a/README.md b/README.md
@@ -1,4 +1,4 @@
-# Turnstile
+# GNU Taler Turnstile
A Drupal module that asks user to subscribe or pay using GNU Taler
before granting access to nodes.
@@ -17,26 +17,26 @@ before granting access to nodes.
## Installation
1. Download and extract the module to your `modules/custom/` directory
-2a. Enable the module via Drush: `drush en turnstile`, or
+2a. Enable the module via Drush: `drush en taler_turnstile`, or
2b. Enable via the Drupal admin interface at `/admin/modules`
## Configuration
-1. Navigate to `/admin/config/content/Turnstile` to configure:
+1. Navigate to `/admin/config/content/taler-turnstile` to configure:
- **Enabled Content Types**: Select which content types should have the price field and access restriction
- **Payment Backend URL**: Taler merchant backend HTTP(S) URL of your payment backend service
- **Access Token**: Authentication token for the Taler merchant backend
-- **Enable access if backend is down**: Disables Turnstile if we cannot
- setup payments with the Taler merchant backend for any reason
+- **Enable access if backend is down**: Disables GNU Taler Turnstile if we
+ cannot setup payments with the Taler merchant backend for any reason
2. Make sure your Taler merchant backend is properly configured:
2a. Bank account added
2b. Legitimization as account owner with payment service provider is done
3. Configure one or more classes of subscriptions (optional)
-Navigate to `/admin/config/system/turnstile/subscription-prices` to configure:
+Navigate to `/admin/config/system/taler-turnstile/subscription-prices` to configure:
- **Subscription prices**: Price for each type of subscription and currency
@@ -66,12 +66,12 @@ display the original content
## File Structure
```
-turnstile/
+taler_turnstile/
├── config/
│ ├── install/
-│ │ └── turnstile.settings.yml - default configuration values
+│ │ └── taler_turnstile.settings.yml - default configuration values
│ └── schema/
-│ └── turnstile.schema.yml - configuration schema (partial, without subscription prices)
+│ └── taler_turnstile.schema.yml - configuration schema (partial, without subscription prices)
├── js/
│ ├── qrcode.min.js - QR code library from https://github.com/davidshimjs/qrcodejs
│ └── payment-button.js - shows QR code and long-polls with backend for payment
@@ -86,15 +86,15 @@ turnstile/
│ ├── PriceCategoryListBuilder.php - Admin list page builder
│ ├── TalerMerchantApiService.php - API service for merchant backend interaction
│ └── TurnstileFieldManager.php - Manages price-category field injection
-├── turnstile.libraries.yml - JS libraries and dependencies
-├── turnstile.info.yml - Module metadata and dependencies
-├── turnstile.install - Install/uninstall hooks
-├── turnstile.module - Hook implementations and debug functions
-├── turnstile.permissions.yml - Permission definitions
-├── turnstile.routing.yml - Route definitions for pages
-├── turnstile.services.yml - Service container definitions
-├── turnstile.links.menu.yml - Menu link to Structure menu
-├── turnstile.links.action.yml - Action link for adding price categories
+├── taler_turnstile.libraries.yml - JS libraries and dependencies
+├── taler_turnstile.info.yml - Module metadata and dependencies
+├── taler_turnstile.install - Install/uninstall hooks
+├── taler_turnstile.module - Hook implementations and debug functions
+├── taler_turnstile.permissions.yml - Permission definitions
+├── taler_turnstile.routing.yml - Route definitions for pages
+├── taler_turnstile.services.yml - Service container definitions
+├── taler_turnstile.links.menu.yml - Menu link to Structure menu
+├── taler_turnstile.links.action.yml - Action link for adding price categories
└── README.md
```
diff --git a/config/install/turnstile.settings.yml b/config/install/taler_turnstile.settings.yml
diff --git a/config/schema/taler_turnstile.schema.yml b/config/schema/taler_turnstile.schema.yml
@@ -0,0 +1,19 @@
+taler_turnstile.settings:
+ type: config_object
+ label: 'GNU Taler Turnstile settings'
+ mapping:
+ enabled_content_types:
+ type: sequence
+ label: 'Enabled content types'
+ sequence:
+ type: string
+ label: 'Content type'
+ payment_backend_url:
+ type: string
+ label: 'Payment backend URL'
+ access_token:
+ type: string
+ label: 'Access token'
+ grant_access_on_error:
+ type: boolean
+ label: 'Disable paywall when payment backend is unavailable'
diff --git a/config/schema/turnstile.schema.yml b/config/schema/turnstile.schema.yml
@@ -1,19 +0,0 @@
-turnstile.settings:
- type: config_object
- label: 'Turnstile settings'
- mapping:
- enabled_content_types:
- type: sequence
- label: 'Enabled content types'
- sequence:
- type: string
- label: 'Content type'
- payment_backend_url:
- type: string
- label: 'Payment backend URL'
- access_token:
- type: string
- label: 'Access token'
- grant_access_on_error:
- type: boolean
- label: 'Disable paywall when payment backend is unavailable'
diff --git a/js/payment-button.js b/js/payment-button.js
@@ -1,6 +1,6 @@
/**
* @file
- * JavaScript for Turnstile payment button functionality.
+ * JavaScript for GNU Taler Turnstile payment button functionality.
*/
(function ($, Drupal, once) {
@@ -126,17 +126,14 @@
/**
* Attach payment button behavior.
*/
- Drupal.behaviors.turnstilePaymentButton = {
+ Drupal.behaviors.talerTurnstilePaymentButton = {
attach: function (context, settings) {
- var buttons = once('turnstile-payment', '.turnstile-pay-button', context);
- buttons.forEach(function(button) {
- var $button = $(button);
- var paymentUrl = $button.attr('href');
- var sessionId = $button.data('session-id');
-
- // Generate QR code
- var $qrContainer = $('.turnstile-qr-code-container', context);
- if ($qrContainer.length && paymentUrl && typeof QRCode !== 'undefined') {
+ var qrContainers = once('taler-turnstile-qr-generation', '.taler-turnstile-qr-code-container', context);
+ qrContainers.forEach(function(qrContainer) {
+ var $qrContainer = $(qrContainer);
+ var paymentUrl = $qrContainer.data('payment-url');
+ var sessionId = $qrContainer.data('session-id');
+ if (paymentUrl && typeof QRCode !== 'undefined') {
$qrContainer.empty();
var talerUri = convertToTalerUri(paymentUrl, sessionId);
new QRCode($qrContainer[0], {
@@ -149,7 +146,6 @@
});
}
- // Start polling for payment status
if (paymentUrl) {
console.log('Starting payment status polling for: ' + paymentUrl);
pollPaymentStatus(paymentUrl, sessionId);
diff --git a/src/Entity/TurnstilePriceCategory.php b/src/Entity/TurnstilePriceCategory.php
@@ -2,10 +2,10 @@
/**
* @file
- * Price category structure for the turnstile module.
+ * Price category structure for the GNU Taler Turnstile module.
*/
-namespace Drupal\turnstile\Entity;
+namespace Drupal\taler_turnstile\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\StringTranslation\StringTranslationTrait;
@@ -14,26 +14,26 @@ use Drupal\Core\StringTranslation\StringTranslationTrait;
* Defines the Price Category entity.
*
* @ConfigEntityType(
- * id = "turnstile_price_category",
+ * id = "taler_turnstile_price_category",
* label = @Translation("Price Category"),
* handlers = {
- * "list_builder" = "Drupal\turnstile\PriceCategoryListBuilder",
+ * "list_builder" = "Drupal\taler_turnstile\PriceCategoryListBuilder",
* "form" = {
- * "add" = "Drupal\turnstile\Form\PriceCategoryForm",
- * "edit" = "Drupal\turnstile\Form\PriceCategoryForm",
- * "delete" = "Drupal\turnstile\Form\PriceCategoryDeleteForm"
+ * "add" = "Drupal\taler_turnstile\Form\PriceCategoryForm",
+ * "edit" = "Drupal\taler_turnstile\Form\PriceCategoryForm",
+ * "delete" = "Drupal\taler_turnstile\Form\PriceCategoryDeleteForm"
* }
* },
- * config_prefix = "turnstile_price_category",
+ * config_prefix = "taler_turnstile_price_category",
* admin_permission = "administer price categories",
* entity_keys = {
* "id" = "id",
* "label" = "label"
* },
* links = {
- * "collection" = "/admin/structure/price-categories",
- * "edit-form" = "/admin/structure/price-categories/{price_category}/edit",
- * "delete-form" = "/admin/structure/price-categories/{price_category}/delete"
+ * "collection" = "/admin/structure/taler-turnstile-price-categories",
+ * "edit-form" = "/admin/structure/taler-turnstile-price-categories/{price_category}/edit",
+ * "delete-form" = "/admin/structure/taler-turnstile-price-categories/{price_category}/delete"
* },
* config_export = {
* "id",
@@ -132,7 +132,7 @@ class TurnstilePriceCategory extends ConfigEntityBase {
* Structure suitable for the choices array in the v1 contract
*/
public function getPaymentChoices(): array {
- $cid = 'turnstile:payment_choices:' . $this->id();
+ $cid = 'taler_turnstile:payment_choices:' . $this->id();
if ($cache = \Drupal::cache()->get($cid)) {
return $cache->data;
}
@@ -208,10 +208,10 @@ class TurnstilePriceCategory extends ConfigEntityBase {
} // for each type of subscription
- // This should return ['config:turnstile_price_category.' . $this->id()];
+ // This should return ['config:taler_turnstile_price_category.' . $this->id()];
$tags = $this->getCacheTags();
// Invalidate cache if getSubscriptionPrice() changes
- $tags[] = 'config:turnstile.settings';
+ $tags[] = 'config:taler_turnstile.settings';
// Invalidate cache also when translations change
$tags[] = 'locale';
\Drupal::cache()->set(
@@ -249,7 +249,7 @@ class TurnstilePriceCategory extends ConfigEntityBase {
* The subscription price (will map to a float), NULL on error
*/
private function getSubscriptionPrice (string $tokenFamilySlug, string $currencyCode) {
- $config = \Drupal::config('turnstile.settings');
+ $config = \Drupal::config('taler_turnstile.settings');
$subscriptions_prices = $config->get('subscription_prices') ?? [];
$subscription_prices = $subscriptions_prices[$tokenFamilySlug] ?? [];
$subscription_price = $subscription_prices[$currencyCode] ?? NULL;
diff --git a/src/Form/PriceCategoryDeleteForm.php b/src/Form/PriceCategoryDeleteForm.php
@@ -7,7 +7,7 @@
* Confirmation form for deleting a price category.
*/
-namespace Drupal\turnstile\Form;
+namespace Drupal\taler_turnstile\Form;
use Drupal\Core\Entity\EntityConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
@@ -31,7 +31,7 @@ class PriceCategoryDeleteForm extends EntityConfirmFormBase {
* {@inheritdoc}
*/
public function getCancelUrl() {
- return new Url('entity.turnstile_price_category.collection');
+ return new Url('entity.taler_turnstile_price_category.collection');
}
/**
diff --git a/src/Form/PriceCategoryForm.php b/src/Form/PriceCategoryForm.php
@@ -7,11 +7,11 @@
* Form handler for price category add and edit forms.
*/
-namespace Drupal\turnstile\Form;
+namespace Drupal\taler_turnstile\Form;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Form\FormStateInterface;
-use Drupal\turnstile\TalerMerchantApiService;
+use Drupal\taler_turnstile\TalerMerchantApiService;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@@ -22,14 +22,14 @@ class PriceCategoryForm extends EntityForm {
/**
* The Turnstile API service.
*
- * @var \Drupal\turnstile\TalerMerchantApiService
+ * @var \Drupal\taler_turnstile\TalerMerchantApiService
*/
protected $apiService;
/**
* Constructs a PriceCategoryForm object.
*
- * @param \Drupal\turnstile\TalerMerchantApiService $api_service
+ * @param \Drupal\taler_turnstile\TalerMerchantApiService $api_service
* The API service.
*/
public function __construct(TalerMerchantApiService $api_service) {
@@ -41,7 +41,7 @@ class PriceCategoryForm extends EntityForm {
*/
public static function create(ContainerInterface $container) {
return new static(
- $container->get('turnstile.api_service')
+ $container->get('taler_turnstile.api_service')
);
}
@@ -66,7 +66,7 @@ class PriceCategoryForm extends EntityForm {
'#type' => 'machine_name',
'#default_value' => $price_category->id(),
'#machine_name' => [
- 'exists' => '\Drupal\turnstile\Entity\TurnstilePriceCategory::load',
+ 'exists' => '\Drupal\taler_turnstile\Entity\TurnstilePriceCategory::load',
],
'#disabled' => !$price_category->isNew(),
];
diff --git a/src/Form/SubscriptionPricesForm.php b/src/Form/SubscriptionPricesForm.php
@@ -1,12 +1,12 @@
<?php
-namespace Drupal\turnstile\Form;
+namespace Drupal\taler_turnstile\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Url;
-use Drupal\turnstile\TalerMerchantApiService;
+use Drupal\taler_turnstile\TalerMerchantApiService;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@@ -17,7 +17,7 @@ class SubscriptionPricesForm extends ConfigFormBase {
/**
* The Taler Merchant API service.
*
- * @var \Drupal\turnstile\TalerMerchantApiService
+ * @var \Drupal\taler_turnstile\TalerMerchantApiService
*/
protected $apiService;
@@ -26,7 +26,7 @@ class SubscriptionPricesForm extends ConfigFormBase {
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The factory for configuration objects.
- * @param \Drupal\turnstile\TalerMerchantApiService $api_service
+ * @param \Drupal\taler_turnstile\TalerMerchantApiService $api_service
* The API service.
*/
public function __construct(ConfigFactoryInterface $config_factory, TalerMerchantApiService $api_service) {
@@ -40,7 +40,7 @@ class SubscriptionPricesForm extends ConfigFormBase {
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.factory'),
- $container->get('turnstile.api_service')
+ $container->get('taler_turnstile.api_service')
);
}
@@ -48,21 +48,21 @@ class SubscriptionPricesForm extends ConfigFormBase {
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
- return ['turnstile.settings'];
+ return ['taler_turnstile.settings'];
}
/**
* {@inheritdoc}
*/
public function getFormId() {
- return 'turnstile_subscription_prices_form';
+ return 'taler_turnstile_subscription_prices_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
- $config = $this->config('turnstile.settings');
+ $config = $this->config('taler_turnstile.settings');
// Check if backend is configured
$backend_url = $config->get('payment_backend_url');
@@ -71,7 +71,7 @@ class SubscriptionPricesForm extends ConfigFormBase {
if (empty($backend_url) || empty($access_token)) {
$this->messenger()->addError(
$this->t('Turnstile payment backend is not configured. Please <a href="@url">configure the backend</a> first.', [
- '@url' => Url::fromRoute('turnstile.settings')->toString(),
+ '@url' => Url::fromRoute('taler_turnstile.settings')->toString(),
])
);
return parent::buildForm($form, $form_state);
@@ -176,7 +176,7 @@ class SubscriptionPricesForm extends ConfigFormBase {
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
- $config = $this->config('turnstile.settings');
+ $config = $this->config('taler_turnstile.settings');
$subscription_prices = $form_state->getValue('subscription_prices');
$config->set('subscription_prices', $subscription_prices);
diff --git a/src/Form/TurnstileSettingsForm.php b/src/Form/TurnstileSettingsForm.php
@@ -1,17 +1,17 @@
<?php
-namespace Drupal\turnstile\Form;
+namespace Drupal\taler_turnstile\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\turnstile\TurnstileFieldManager;
-use Drupal\turnstile\TalerMerchantApiService;
+use Drupal\taler_turnstile\TurnstileFieldManager;
+use Drupal\taler_turnstile\TalerMerchantApiService;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
- * Configure Turnstile settings.
+ * Configure GNU Taler Turnstile settings.
*/
class TurnstileSettingsForm extends ConfigFormBase {
@@ -25,14 +25,14 @@ class TurnstileSettingsForm extends ConfigFormBase {
/**
* The Turnstile field manager.
*
- * @var \Drupal\turnstile\TurnstileFieldManager
+ * @var \Drupal\taler_turnstile\TurnstileFieldManager
*/
protected $fieldManager;
/**
* The Taler Merchant API service.
*
- * @var \Drupal\turnstile\TalerMerchantApiService
+ * @var \Drupal\taler_turnstile\TalerMerchantApiService
*/
protected $apiService;
@@ -43,9 +43,9 @@ class TurnstileSettingsForm extends ConfigFormBase {
* The factory for configuration objects.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
- * @param \Drupal\turnstile\TurnstileFieldManager $field_manager
+ * @param \Drupal\taler_turnstile\TurnstileFieldManager $field_manager
* The field manager.
- * @param \Drupal\turnstile\TalerMerchantApiService $api_service
+ * @param \Drupal\taler_turnstile\TalerMerchantApiService $api_service
* The API service.
*/
public function __construct(ConfigFactoryInterface $config_factory, EntityTypeManagerInterface $entity_type_manager, TurnstileFieldManager $field_manager, TalerMerchantApiService $api_service) {
@@ -62,8 +62,8 @@ class TurnstileSettingsForm extends ConfigFormBase {
return new static(
$container->get('config.factory'),
$container->get('entity_type.manager'),
- $container->get('turnstile.field_manager'),
- $container->get('turnstile.api_service')
+ $container->get('taler_turnstile.field_manager'),
+ $container->get('taler_turnstile.api_service')
);
}
@@ -71,21 +71,21 @@ class TurnstileSettingsForm extends ConfigFormBase {
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
- return ['turnstile.settings'];
+ return ['taler_turnstile.settings'];
}
/**
* {@inheritdoc}
*/
public function getFormId() {
- return 'turnstile_settings_form';
+ return 'taler_turnstile_settings_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
- $config = $this->config('turnstile.settings');
+ $config = $this->config('taler_turnstile.settings');
// Get all available content types.
$content_types = $this->entityTypeManager->getStorage('node_type')->loadMultiple();
@@ -214,7 +214,7 @@ class TurnstileSettingsForm extends ConfigFormBase {
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
- $config = $this->config('turnstile.settings');
+ $config = $this->config('taler_turnstile.settings');
// Test the access token and backend URL.
$payment_backend_url = $form_state->getValue('payment_backend_url');
diff --git a/src/PriceCategoryListBuilder.php b/src/PriceCategoryListBuilder.php
@@ -7,7 +7,7 @@
* List builder for price categories.
*/
-namespace Drupal\turnstile;
+namespace Drupal\taler_turnstile;
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
use Drupal\Core\Entity\EntityInterface;
@@ -31,7 +31,7 @@ class PriceCategoryListBuilder extends ConfigEntityListBuilder {
* {@inheritdoc}
*/
public function buildRow(EntityInterface $entity) {
- /** @var \Drupal\turnstile\Entity\TurnstilePriceCategory $entity */
+ /** @var \Drupal\taler_turnstile\Entity\TurnstilePriceCategory $entity */
$row['label'] = $entity->label();
$row['id'] = $entity->id();
$row['description'] = $entity->getDescription();
diff --git a/src/TalerMerchantApiService.php b/src/TalerMerchantApiService.php
@@ -7,12 +7,12 @@
* Service for interacting with the Taler Merchant Backend.
*/
-namespace Drupal\turnstile;
+namespace Drupal\taler_turnstile;
use Drupal\Core\Http\ClientFactory;
use Drupal\node\NodeInterface;
use Psr\Log\LoggerInterface;
-use Drupal\turnstile\Entity\TurnstilePriceCategory;
+use Drupal\taler_turnstile\Entity\TurnstilePriceCategory;
use GuzzleHttp\Exception\RequestException;
use Drupal\Core\StringTranslation\StringTranslationTrait;
@@ -174,7 +174,7 @@ class TalerMerchantApiService {
* Array mapping token family IDs to subscription data each with a 'name' and 'label' (usually the slug), 'description' and 'description_i18n'.
*/
public function getSubscriptions() {
- $cid = 'turnstile:subscriptions';
+ $cid = 'taler_turnstile:subscriptions';
if ($cache = \Drupal::cache()->get($cid)) {
return $cache->data;
}
@@ -192,13 +192,13 @@ class TalerMerchantApiService {
'description' => $description,
'description_i18n' => $description_i18n,
];
- $config = \Drupal::config('turnstile.settings');
+ $config = \Drupal::config('taler_turnstile.settings');
$backend_url = $config->get('payment_backend_url');
$access_token = $config->get('access_token');
if (empty($backend_url) ||
empty($access_token)) {
- $this->logger->debug('No Turnstile backend configured, returning "none" for subscriptions.');
+ $this->logger->debug('No GNU Taler Turnstile backend configured, returning "none" for subscriptions.');
return $result;
}
@@ -231,7 +231,7 @@ class TalerMerchantApiService {
// empty list
return $result;
case 403:
- $this->logger->warning('Access denied by the merchant backend. Did your credentials change or expire? Check your Turnstile configuration!');
+ $this->logger->warning('Access denied by the merchant backend. Did your credentials change or expire? Check your GNU Taler Turnstile configuration!');
return $result;
case 404:
$body_log_fmt = json_encode($jbody, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
@@ -274,12 +274,12 @@ class TalerMerchantApiService {
* and 'step' (typically 0 for JPY or 0.01 for EUR/USD).
*/
public function getCurrencies() {
- $cid = 'turnstile:currencies';
+ $cid = 'taler_turnstile:currencies';
if ($cache = \Drupal::cache()->get($cid)) {
return $cache->data;
}
- $config = \Drupal::config('turnstile.settings');
+ $config = \Drupal::config('taler_turnstile.settings');
$payment_backend_url = $config->get('payment_backend_url');
if (empty($payment_backend_url)) {
@@ -354,13 +354,13 @@ class TalerMerchantApiService {
* Order status information or FALSE on failure.
*/
public function checkOrderStatus($order_id) {
- $config = \Drupal::config('turnstile.settings');
+ $config = \Drupal::config('taler_turnstile.settings');
$backend_url = $config->get('payment_backend_url');
$access_token = $config->get('access_token');
if (empty($backend_url) ||
empty($access_token)) {
- $this->logger->debug('No Turnstile backend configured, cannot check order status!');
+ $this->logger->debug('No GNU Taler Turnstile backend configured, cannot check order status!');
return FALSE;
}
@@ -387,7 +387,7 @@ class TalerMerchantApiService {
// Success, handled below
break;
case 403:
- $this->logger->warning('Access denied by the merchant backend. Did your credentials change or expire? Check your Turnstile configuration!');
+ $this->logger->warning('Access denied by the merchant backend. Did your credentials change or expire? Check your GNU Taler Turnstile configuration!');
return FALSE;
case 404:
// Order unknown or instance unknown
@@ -399,12 +399,12 @@ class TalerMerchantApiService {
// Protocol violation. Could happen if the backend domain was
// taken over by someone else.
$body_log_fmt = json_encode($jbody, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
- $this->logger->error('Invalid response from merchant backend when trying to obtain order status. Check your Turnstile configuration! @body', ['@body' => $body_log_fmt ?? 'N/A']);
+ $this->logger->error('Invalid response from merchant backend when trying to obtain order status. Check your GNU Taler Turnstile configuration! @body', ['@body' => $body_log_fmt ?? 'N/A']);
return FALSE;
case TalerErrorCode::TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN:
// This could happen if our instance was deleted after the configuration was
// checked. Very bad, log serious error.
- $this->logger->error('Configured instance "@detail" unknown to merchant backend. Check your Turnstile configuration!', ['@detail' => $jbody['detail'] ?? 'N/A']);
+ $this->logger->error('Configured instance "@detail" unknown to merchant backend. Check your GNU Taler Turnstile configuration!', ['@detail' => $jbody['detail'] ?? 'N/A']);
return FALSE;
case TalerErrorCode::TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN:
// This could happen if the instance owner manually deleted
@@ -471,7 +471,7 @@ class TalerMerchantApiService {
($signature_validity_end > $now) )
{
// Theoretically, one contract could buy multiple
- // subscriptions. But Turnstile does not
+ // subscriptions. But GNU Taler Turnstile does not
// generate such contracts and we do not support
// that case here.
$subscription_slug = $slug;
@@ -519,7 +519,7 @@ class TalerMerchantApiService {
* Order information or FALSE on failure.
*/
public function createOrder(NodeInterface $node) {
- $config = \Drupal::config('turnstile.settings');
+ $config = \Drupal::config('taler_turnstile.settings');
$backend_url = $config->get('payment_backend_url');
$access_token = $config->get('access_token');
@@ -529,7 +529,7 @@ class TalerMerchantApiService {
}
/** @var \Drupal\Core\Field\EntityReferenceFieldItemList $field */
- $field = $node->get('field_turnstile_price_category');
+ $field = $node->get('field_taler_turnstile_prcat');
if ($field->isEmpty()) {
$this->logger->debug('No price category selected');
return FALSE;
@@ -601,7 +601,7 @@ class TalerMerchantApiService {
// Success, handled below
break;
case 403:
- $this->logger->warning('Access denied by the merchant backend. Did your credentials change or expire? Check your Turnstile configuration!');
+ $this->logger->warning('Access denied by the merchant backend. Did your credentials change or expire? Check your GNU Taler Turnstile configuration!');
return FALSE;
case 404:
$body_log_fmt = json_encode($jbody, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
@@ -609,8 +609,8 @@ class TalerMerchantApiService {
return FALSE;
case 409:
case 410:
- // 409: We didn't specify an order, so this should be "wrong currency", which again Turnstile tries to prevent. So this shouldn't be possible.
- // 410: We didn't specify a product, so out-of-stock should also be impossible for Turnstile
+ // 409: We didn't specify an order, so this should be "wrong currency", which again GNU Taler Turnstile tries to prevent. So this shouldn't be possible.
+ // 410: We didn't specify a product, so out-of-stock should also be impossible for GNU Taler Turnstile
$body_log_fmt = json_encode($jbody, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
$this->logger->error('Unexpected HTTP status code @status trying to create order: @hint (@detail, #@ec): @body', ['@status' => $http_status, '@hint' => $jbody['hint'] ?? 'N/A', '@ec' => $jbody['code'] ?? 'N/A', '@detail' => $jbody['detail'] ?? 'N/A', '@body' => $body_log_fmt ?? 'N/A']);
return FALSE;
diff --git a/src/TurnstileFieldManager.php b/src/TurnstileFieldManager.php
@@ -1,6 +1,6 @@
<?php
-namespace Drupal\turnstile;
+namespace Drupal\taler_turnstile;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\field\Entity\FieldStorageConfig;
@@ -10,7 +10,7 @@ use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
- * Service for managing Turnstile fields on content types.
+ * Service for managing GNU Taler Turnstile fields on content types.
*/
class TurnstileFieldManager {
@@ -37,7 +37,7 @@ class TurnstileFieldManager {
}
/**
- * Add Turnstile fields to specified content types.
+ * Add GNU Taler Turnstile fields to specified content types.
*
* @param array $bundles
* Array of content type machine names.
@@ -61,15 +61,15 @@ class TurnstileFieldManager {
*/
protected function ensureFieldStorageExists() {
// Create price category field storage if it doesn't exist.
- $field_category_storage = FieldStorageConfig::loadByName('node', 'field_turnstile_price_category');
+ $field_category_storage = FieldStorageConfig::loadByName('node', 'field_taler_turnstile_prcat');
if (!$field_category_storage) {
$field_category_storage = FieldStorageConfig::create([
- 'field_name' => 'field_turnstile_price_category',
+ 'field_name' => 'field_taler_turnstile_prcat',
'entity_type' => 'node',
'type' => 'entity_reference',
'cardinality' => 1,
'settings' => [
- 'target_type' => 'turnstile_price_category',
+ 'target_type' => 'taler_turnstile_price_category',
],
]);
$field_category_storage->save();
@@ -83,13 +83,13 @@ class TurnstileFieldManager {
* The bundle machine name.
*/
protected function addPriceCategoryField($bundle) {
- $field_category_storage = FieldStorageConfig::loadByName('node', 'field_turnstile_price_category');
+ $field_category_storage = FieldStorageConfig::loadByName('node', 'field_taler_turnstile_prcat');
- $existing_field = FieldConfig::loadByName('node', $bundle, 'field_turnstile_price_category');
+ $existing_field = FieldConfig::loadByName('node', $bundle, 'field_taler_turnstile_prcat');
if (!$existing_field) {
// Create field configuration.
$field_config = FieldConfig::create([
- 'field_name' => 'field_turnstile_price_category',
+ 'field_name' => 'field_taler_turnstile_prcat',
'entity_type' => 'node',
'field_storage' => $field_category_storage,
'bundle' => $bundle,
@@ -97,7 +97,7 @@ class TurnstileFieldManager {
'description' => $this->t('Select a price category for this content.'),
'required' => FALSE,
'settings' => [
- 'handler' => 'default:turnstile_price_category',
+ 'handler' => 'default:taler_turnstile_price_category',
'handler_settings' => [
'target_bundles' => NULL,
'sort' => [
@@ -113,7 +113,7 @@ class TurnstileFieldManager {
// Add to form display.
$form_display = EntityFormDisplay::load('node.' . $bundle . '.default');
if ($form_display) {
- $form_display->setComponent('field_turnstile_price_category', [
+ $form_display->setComponent('field_taler_turnstile_prcat', [
'type' => 'options_select',
'weight' => 10,
'settings' => [],
@@ -124,7 +124,7 @@ class TurnstileFieldManager {
// Add to view display.
$view_display = EntityViewDisplay::load('node.' . $bundle . '.default');
if ($view_display) {
- $view_display->setComponent('field_turnstile_price_category', [
+ $view_display->setComponent('field_taler_turnstile_prcat', [
'type' => 'entity_reference_label',
'weight' => 10,
'label' => 'above',
@@ -138,14 +138,14 @@ class TurnstileFieldManager {
}
/**
- * Remove Turnstile fields from specified content types.
+ * Remove GNU Taler 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_turnstile_price_category');
+ $field_config = FieldConfig::loadByName('node', $bundle, 'field_taler_turnstile_prcat');
if ($field_config) {
$field_config->delete();
}
@@ -153,14 +153,14 @@ class TurnstileFieldManager {
// Remove from form display.
$form_display = EntityFormDisplay::load('node.' . $bundle . '.default');
if ($form_display) {
- $form_display->removeComponent('field_turnstile_price_category');
+ $form_display->removeComponent('field_taler_turnstile_prcat');
$form_display->save();
}
// Remove from view display.
$view_display = EntityViewDisplay::load('node.' . $bundle . '.default');
if ($view_display) {
- $view_display->removeComponent('field_turnstile_price_category');
+ $view_display->removeComponent('field_taler_turnstile_prcat');
$view_display->save();
}
}
@@ -172,7 +172,7 @@ class TurnstileFieldManager {
* Clean up field storage if no content types are using it.
*/
protected function cleanupFieldStorage() {
- $field_category_storage = FieldStorageConfig::loadByName('node', 'field_turnstile_price_category');
+ $field_category_storage = FieldStorageConfig::loadByName('node', 'field_taler_turnstile_prcat');
if ($field_category_storage) {
$field_configs = $this->entityTypeManager
->getStorage('field_config')
diff --git a/taler_turnstile.info.yml b/taler_turnstile.info.yml
@@ -0,0 +1,11 @@
+name: GNU Taler Turnstile
+type: module
+description: 'Adds price field to nodes and requires payment for access.'
+core_version_requirement: ^9 || ^10
+package: System
+version: '0.9.0'
+dependencies:
+ - drupal:node
+ - drupal:field
+ - drupal:user
+configure: taler_turnstile.settings
diff --git a/taler_turnstile.install b/taler_turnstile.install
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the Turnstile module.
+ */
+
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\Core\Entity\Entity\EntityFormDisplay;
+use Drupal\Core\Entity\Entity\EntityViewDisplay;
+use Drupal\taler_turnstile\TurnstileFieldManager;
+
+/**
+ * Implements hook_install().
+ */
+function taler_turnstile_install() {
+ $config = \Drupal::config('taler_turnstile.settings');
+ $enabled_types = $config->get('enabled_content_types') ?: ['article'];
+
+ /** @var TurnstileFieldManager $field_manager */
+ $field_manager = \Drupal::service('taler_turnstile.field_manager');
+ $field_manager->addFieldsToContentTypes($enabled_types);
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function taler_turnstile_uninstall() {
+ /** @var TurnstileFieldManager $field_manager */
+ $field_manager = \Drupal::service('taler_turnstile.field_manager');
+
+ $config = \Drupal::config('taler_turnstile.settings');
+ $enabled_types = $config->get('enabled_content_types') ?: [];
+ if (!empty($enabled_types)) {
+ $field_manager->removeFieldsFromContentTypes($enabled_types);
+ }
+
+ // Clean up configuration.
+ \Drupal::configFactory()->getEditable('taler_turnstile.settings')->delete();
+}
+\ No newline at end of file
diff --git a/taler_turnstile.libraries.yml b/taler_turnstile.libraries.yml
@@ -0,0 +1,14 @@
+payment_button:
+ version: 1.x
+ js:
+ js/payment-button.js: {}
+ dependencies:
+ - core/jquery
+ - core/drupal
+ - core/once
+ - taler_turnstile/qrcode
+
+qrcode:
+ version: 1.x
+ js:
+ js/qrcode.min.js: { minified: true }
diff --git a/taler_turnstile.links.action.yml b/taler_turnstile.links.action.yml
@@ -0,0 +1,7 @@
+# Action links for adding new price categories.
+
+entity.taler_turnstile_price_category.add_form:
+ route_name: entity.taler_turnstile_price_category.add_form
+ title: 'Add price category'
+ appears_on:
+ - entity.taler_turnstile_price_category.collection
diff --git a/taler_turnstile.links.menu.yml b/taler_turnstile.links.menu.yml
@@ -0,0 +1,20 @@
+taler_turnstile.settings:
+ title: 'GNU Taler Turnstile basics'
+ description: 'Configure GNU Taler payment backend and paid content types.'
+ parent: system.admin_config_system
+ route_name: taler_turnstile.settings
+ weight: 98
+
+taler_turnstile.subscription_prices:
+ title: 'GNU Taler Turnstile subscription prices'
+ description: 'Configure prices for Turnstile subscriptions.'
+ parent: system.admin_config_system
+ route_name: taler_turnstile.subscription_prices
+ weight: 99
+
+taler_turnstile.taler_turnstile_price_category.collection:
+ title: 'GNU Taler Turnstile price categories'
+ route_name: entity.taler_turnstile_price_category.collection
+ description: 'Manage price categories for the GNU Taler Turnstile.'
+ parent: system.admin_structure
+ weight: 10
diff --git a/taler_turnstile.module b/taler_turnstile.module
@@ -0,0 +1,290 @@
+<?php
+
+/**
+ * @file
+ * Main module file for Turnstile.
+ */
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
+use Drupal\node\NodeInterface;
+
+
+/**
+ * Implements hook_form_FORM_ID_alter() for node forms. Adds a
+ * description for the Turnstile price category field.
+ */
+function taler_turnstile_form_node_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
+ $node = $form_state->getFormObject()->getEntity();
+ $config = \Drupal::config('taler_turnstile.settings');
+ $enabled_types = $config->get('enabled_content_types') ?: [];
+
+ // Only show price field on enabled content types.
+ if (! in_array($node->bundle(), $enabled_types)) {
+ return;
+ }
+ if (! isset($form['field_taler_turnstile_prcat'])) {
+ return;
+ }
+ $form['field_taler_turnstile_prcat']['#group'] = 'meta';
+ $form['field_taler_turnstile_prcat']['widget'][0]['value']['#description'] = t('Set a price category to enable paywall protection for this content.');
+
+ // Load all price categories for the description.
+ $price_categories = \Drupal::entityTypeManager()
+ ->getStorage('taler_turnstile_price_category')
+ ->loadMultiple();
+
+ $category_list = [];
+ foreach ($price_categories as $category) {
+ $category_list[] = $category->label() . ': ' . $category->getDescription();
+ }
+
+ $description = t('Select a price category to enable paywall protection for this content.');
+ if (!empty($category_list)) {
+ $description .= '<br><br><strong>' . t('Available categories:') . '</strong><ul><li>'
+ . implode('</li><li>', $category_list) . '</li></ul>';
+ }
+
+ $form['field_taler_turnstile_prcat']['widget']['#description'] = $description;
+}
+
+
+/**
+ * Implements hook_entity_view_alter(). Transforms the body of an entity to
+ * show the Turnstile dialog instead of the full body if the user needs
+ * to pay to see the full article.
+ */
+function taler_turnstile_entity_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
+ // Only process nodes with turnstile enabled
+ if ($entity->getEntityTypeId() !== 'node') {
+ return;
+ }
+ /** @var \Drupal\node\NodeInterface $node */
+ $node = $entity;
+
+ if (!$node->hasField('field_taler_turnstile_prcat')) {
+ return;
+ }
+
+ /** @var \Drupal\Core\Field\EntityReferenceFieldItemList $field */
+ $field = $node->get('field_taler_turnstile_prcat');
+ if ($field->isEmpty()) {
+ \Drupal::logger('taler_turnstile')->debug('No price category selected');
+ return FALSE;
+ }
+
+ /** @var TurnstilePriceCategory $price_category */
+ $price_category = $field->entity;
+ if (! $price_category) {
+ \Drupal::logger('taler_turnstile')->debug('Node has no price category, skipping payment.');
+ return;
+ }
+
+ $view_mode = $display->getMode();
+ if ($view_mode !== 'full') {
+ \Drupal::logger('taler_turnstile')->debug('Turnstile only active for "Full" view mode.');
+ return;
+ }
+
+ $subscriptions = $price_category->getFullSubscriptions();
+ foreach ($subscriptions as $subscription_id) {
+ if (_taler_turnstile_is_subscriber ($subscription_id)) {
+ \Drupal::logger('taler_turnstile')->debug('Subscriber detected, granting access.');
+ return;
+ }
+ }
+
+ // Disable page cache, this page is personalized!
+ \Drupal::service('page_cache_kill_switch')->trigger();
+
+ $node_id = $node->id();
+ if (_taler_turnstile_has_session_access($node_id)) {
+ \Drupal::logger('taler_turnstile')->debug('Session has access to this node.');
+ return;
+ }
+
+ /** @var \Drupal\taler_turnstile\TalerMerchantApiService $api_service */
+ $api_service = \Drupal::service('taler_turnstile.api_service');
+
+ $order_info = _taler_turnstile_get_node_order_info ($node_id);
+ if ($order_info) {
+ \Drupal::logger('taler_turnstile')->debug('Found existing order @ORDER_ID for this session.', [ '@ORDER_ID' => $order_info['order_id'] ]);
+ // We have an existing order, check if it was paid
+ $order_id = $order_info['order_id'];
+ $order_status = $api_service->checkOrderStatus($order_info['order_id']);
+ if ($order_status && $order_status['paid']) {
+ \Drupal::logger('taler_turnstile')->debug('Order was paid, granting session access.');
+ _taler_turnstile_grant_session_access($node_id);
+ if ($order_status['subscription_slug'] ?? FALSE) {
+ \Drupal::logger('taler_turnstile')->debug('Subscription was purchased, granting subscription access.');
+ $subscription_slug = $order_status['subscription_slug'];
+ $expiration = $order_status['subscription_expiration'];
+ _taler_turnstile_grant_subscriber_access ($subscription_slug, $expiration);
+ }
+ return;
+ }
+ if ($order_status &&
+ ($order_status['order_expiration'] ?? 0) < time() + 60) {
+ // If order expired (or would expire in less than one minute,
+ // so too soon for the user to still pay it), then ignore it!
+ $order_info = NULL;
+ }
+ if (!$order_status)
+ {
+ $order_info = NULL;
+ }
+ else
+ {
+ \Drupal::logger('taler_turnstile')->debug('Order expires in @future seconds, not creating new one.', ['@future' => ($order_status['order_expiration'] ?? 0) - time ()] );
+ }
+ }
+ if (!$order_info) {
+ // Need to try to create a new order
+ $order_info = $api_service->createOrder($node);
+ }
+ if (!$order_info) {
+ \Drupal::logger('taler_turnstile')->warning('Failed to setup order with Taler merchant backend!');
+ $config = \Drupal::config('taler_turnstile.settings');
+ $grant_access_on_error = $config->get('grant_access_on_error') ?? TRUE;
+ if ($grant_access_on_error) {
+ \Drupal::logger('taler_turnstile')->debug('Could not setup order, disabling Turnstile.');
+ return;
+ }
+ $pay_button = [
+ '#markup' => '<div class="taler-turnstile-error">' . t('Payment system temporarily unavailable. Please try again later.') . '</div>',
+ ];
+ }
+ else
+ {
+ _taler_turnstile_store_order_node_mapping($node_id, $order_info);
+ $pay_button = [
+ '#theme' => 'taler_turnstile_payment_button',
+ '#order_id' => $order_info['order_id'],
+ '#session_id' => $order_info['session_id'],
+ '#payment_url' => $order_info['payment_url'],
+ '#node_title' => $node->getTitle(),
+ '#attached' => [
+ 'library' => ['taler_turnstile/payment_button'],
+ ],
+ ];
+ }
+ // User needs to pay - replace full content with teaser + payment button
+ // Generate teaser view mode
+ $view_builder = \Drupal::entityTypeManager()->getViewBuilder('node');
+ $teaser_build = $view_builder->view($entity, 'teaser');
+
+ // Replace the build array with teaser content
+ // Keep important metadata from original build (?)
+ $build = [
+ '#cache' => ['contexts' => ['url']],
+ '#weight' => $build['#weight'] ?? 0,
+ ];
+
+ // Add teaser content
+ $build['teaser'] = [
+ '#type' => 'container',
+ '#attributes' => ['class' => ['taler-turnstile-teaser-wrapper']],
+ 'content' => $teaser_build,
+ '#weight' => 0,
+ ];
+
+ // Add payment button
+ $build['payment_button'] = [
+ '#type' => 'container',
+ '#attributes' => ['class' => ['taler-turnstile-payment-wrapper']],
+ 'button' => $pay_button,
+ '#weight' => 10,
+ ];
+}
+
+
+/**
+ * Helper function to grant subscription access for this
+ * visitor to the given node ID until the given expiration time.
+ */
+function _taler_turnstile_grant_subscriber_access($subscription_slug, $expiration) {
+ $session = \Drupal::request()->getSession();
+ $access_data = $session->get('taler_turnstile_subscriptions', []);
+ $access_data[$subscription_slug] = $expiration;
+ $session->set('taler_turnstile_subscriptions', $access_data);
+}
+
+
+/**
+ * Helper function to check if this session is currently
+ * subscribed on the given type of subscription.
+ */
+function _taler_turnstile_is_subscriber($subscription_slug) {
+ $session = \Drupal::request()->getSession();
+ $access_data = $session->get('taler_turnstile_subscriptions', []);
+ return ($access_data[$subscription_slug] ?? 0) >= time();
+}
+
+
+/**
+ * Helper function to grant session access for this
+ * visitor to the given node ID.
+ */
+function _taler_turnstile_grant_session_access($node_id) {
+ $session = \Drupal::request()->getSession();
+ $access_data = $session->get('taler_turnstile_access', []);
+ $access_data[$node_id] = TRUE;
+ $session->set('taler_turnstile_access', $access_data);
+}
+
+
+/**
+ * Helper function to check session access. Checks if this
+ * visitor has been granted access to the given $node_id.
+ */
+function _taler_turnstile_has_session_access($node_id) {
+ $session = \Drupal::request()->getSession();
+ $access_data = $session->get('taler_turnstile_access', []);
+ return $access_data[$node_id] ?? FALSE;
+}
+
+
+/**
+ * Store the mapping between order_id and node_id.
+ * Uses session to track which orders belong to which nodes.
+ */
+function _taler_turnstile_store_order_node_mapping($node_id, $order_info) {
+ $session = \Drupal::request()->getSession();
+ $node_orders = $session->get('taler_turnstile_node_orders', []);
+ $node_orders[$node_id] = $order_info;
+ $session->set('taler_turnstile_node_orders', $node_orders);
+}
+
+
+/**
+ * Get the order_info associated with a node_id.
+ */
+function _taler_turnstile_get_node_order_info($node_id) {
+ $session = \Drupal::request()->getSession();
+ $node_orders = $session->get('taler_turnstile_node_orders', []);
+ return $node_orders[$node_id] ?? NULL;
+}
+
+
+/**
+ * Implements hook_theme().
+ */
+function taler_turnstile_theme() {
+ return [
+ 'taler_turnstile_payment_button' => [
+ 'variables' => [
+ 'order_id' => NULL,
+ 'session_id' => NULL,
+ 'payment_url' => NULL,
+ 'node_title' => NULL,
+ ],
+ 'template' => 'taler-turnstile-payment-button',
+ ],
+ 'taler_turnstile_settings' => [
+ 'variables' => [
+ 'config' => NULL,
+ ],
+ ],
+ ];
+}
diff --git a/taler_turnstile.permissions.yml b/taler_turnstile.permissions.yml
@@ -0,0 +1,9 @@
+administer taler_turnstile:
+ title: 'Administer GNU Taler Turnstile'
+ description: 'Configure Turnstile settings and manage payment options.'
+ restrict access: true
+
+administer price categories:
+ title: 'Administer price categories'
+ description: 'Create, edit, and delete price categories.'
+ restrict access: true
diff --git a/taler_turnstile.routing.yml b/taler_turnstile.routing.yml
@@ -0,0 +1,53 @@
+taler_turnstile.settings:
+ path: '/admin/config/system/taler-turnstile'
+ defaults:
+ _form: '\Drupal\taler_turnstile\Form\TurnstileSettingsForm'
+ _title: 'GNU Taler Turnstile settings'
+ requirements:
+ _permission: 'administer GNU Taler Turnstile'
+ options:
+ _admin_route: TRUE
+
+# Route for editing subscription prices
+taler_turnstile.subscription_prices:
+ path: '/admin/config/system/taler_turnstile/subscription-prices'
+ defaults:
+ _form: '\Drupal\taler_turnstile\Form\SubscriptionPricesForm'
+ _title: 'Subscription prices'
+ requirements:
+ _permission: 'administer GNU Taler Turnstile'
+ options:
+ _admin_route: TRUE
+
+# Routes for price categories.
+entity.taler_turnstile_price_category.collection:
+ path: '/admin/structure/taler-turnstile-price-categories'
+ defaults:
+ _entity_list: 'taler_turnstile_price_category'
+ _title: 'Price categories'
+ requirements:
+ _permission: 'administer price categories'
+
+entity.taler_turnstile_price_category.add_form:
+ path: '/admin/structure/taler-turnstile-price-categories/add'
+ defaults:
+ _entity_form: 'taler_turnstile_price_category.add'
+ _title: 'Add price category'
+ requirements:
+ _permission: 'administer price categories'
+
+entity.taler_turnstile_price_category.edit_form:
+ path: '/admin/structure/taler-turnstile-price-categories/{taler_turnstile_price_category}/edit'
+ defaults:
+ _entity_form: 'taler_turnstile_price_category.edit'
+ _title: 'Edit price category'
+ requirements:
+ _permission: 'administer price categories'
+
+entity.taler_turnstile_price_category.delete_form:
+ path: '/admin/structure/taler-turnstile-price-categories/{taler_turnstile_price_category}/delete'
+ defaults:
+ _entity_form: 'taler_turnstile_price_category.delete'
+ _title: 'Delete price category'
+ requirements:
+ _permission: 'administer price categories'
diff --git a/taler_turnstile.services.yml b/taler_turnstile.services.yml
@@ -0,0 +1,12 @@
+services:
+ taler_turnstile.api_service:
+ class: Drupal\taler_turnstile\TalerMerchantApiService
+ arguments: ['@http_client_factory', '@logger.channel.taler_turnstile']
+
+ taler_turnstile.field_manager:
+ class: Drupal\taler_turnstile\TurnstileFieldManager
+ arguments: ['@entity_type.manager']
+
+ logger.channel.taler_turnstile:
+ parent: logger.channel_base
+ arguments: ['taler-turnstile']
diff --git a/templates/taler-turnstile-payment-button.html.twig b/templates/taler-turnstile-payment-button.html.twig
@@ -0,0 +1,157 @@
+<div class="taler-turnstile-payment-container">
+ <div class="taler-turnstile-payment-info">
+ <h3>{{ 'Payment required'|t }}</h3>
+ <p>{{ 'Please pay to access'|t }} <strong>{{ node_title }}</strong>.</p>
+ </div>
+
+ <div class="taler-turnstile-payment-actions">
+ <div class="taler-turnstile-payment-qr">
+ <div class="taler-turnstile-qr-code-container"
+ data-payment-url="{{ payment_url }}"
+ data-order-id="{{ order_id }}"
+ data-session-id="{{ session_id }}"></div>
+ <p class="taler-turnstile-qr-help">{{ 'Scan with your GNU Taler wallet'|t }}</p>
+ </div>
+
+ <div class="taler-turnstile-payment-or">
+ <span>{{ 'or'|t }}</span>
+ </div>
+
+ <a href="{{ payment_url }}"
+ class="button button--primary taler-turnstile-pay-button"
+ data-order-id="{{ order_id }}"
+ data-session-id="{{ session_id }}">
+ {{ 'Open GNU Taler payment Web page'|t }}
+ </a>
+ </div>
+
+ <div class="taler-turnstile-payment-status">
+ <p class="taler-turnstile-status-message">{{ 'Waiting for payment...'|t }}</p>
+ </div>
+
+</div>
+
+<style>
+.taler-turnstile-payment-container {
+ border: 2px solid #e0e0e0;
+ border-radius: 8px;
+ padding: 2rem;
+ margin: 2rem 0;
+ background: #f9f9f9;
+}
+
+.taler-turnstile-payment-info h3 {
+ margin-top: 0;
+ color: #333;
+}
+
+.taler-turnstile-price {
+ font-size: 1.2rem;
+ font-weight: bold;
+ color: #0066cc;
+ margin: 1rem 0;
+}
+
+.taler-turnstile-payment-actions {
+ margin-top: 1.5rem;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 1rem;
+}
+
+.taler-turnstile-payment-qr {
+ text-align: center;
+ padding: 1rem;
+ background: white;
+ border-radius: 4px;
+ border: 1px solid #ddd;
+}
+
+.taler-turnstile-qr-code {
+ display: block;
+ margin: 0 auto;
+ max-width: 200px;
+ height: auto;
+}
+
+.taler-turnstile-qr-help {
+ margin: 0.5rem 0 0 0;
+ font-size: 0.9rem;
+ color: #666;
+}
+
+.taler-turnstile-payment-or {
+ margin: 0.5rem 0;
+ color: #666;
+ font-weight: bold;
+}
+
+.taler-turnstile-pay-button {
+ display: inline-block;
+ padding: 0.75rem 2rem;
+ background: #0066cc;
+ color: white;
+ text-decoration: none;
+ border-radius: 4px;
+ font-weight: bold;
+ transition: background 0.3s;
+}
+
+.taler-turnstile-pay-button:hover {
+ background: #0052a3;
+ color: white;
+}
+
+.taler-turnstile-payment-status {
+ margin-top: 1.5rem;
+ padding: 1rem;
+ background: #e3f2fd;
+ border: 1px solid #90caf9;
+ border-radius: 4px;
+ text-align: center;
+}
+
+.taler-turnstile-status-message {
+ margin: 0;
+ color: #1565c0;
+ font-style: italic;
+}
+
+.taler-turnstile-access-message {
+ padding: 1rem;
+ margin: 1rem 0;
+ background: #fff3cd;
+ border: 1px solid #ffc107;
+ border-radius: 4px;
+}
+
+.taler-turnstile-teaser-wrapper {
+ position: relative;
+ max-height: 400px;
+ overflow: hidden;
+}
+
+.taler-turnstile-teaser-wrapper::after {
+ content: '';
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 100px;
+ background: linear-gradient(to bottom, transparent, white);
+}
+
+/* Responsive design */
+@media (min-width: 768px) {
+ .taler-turnstile-payment-actions {
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+ }
+
+ .taler-turnstile-payment-or {
+ margin: 0 1rem;
+ }
+}
+</style>
diff --git a/templates/turnstile-payment-button.html.twig b/templates/turnstile-payment-button.html.twig
@@ -1,156 +0,0 @@
-<div class="turnstile-payment-container">
- <div class="turnstile-payment-info">
- <h3>{{ 'Payment required'|t }}</h3>
- <p>{{ 'Please pay to access'|t }} <strong>{{ node_title }}</strong>.</p>
- </div>
-
- <div class="turnstile-payment-actions">
- <div class="turnstile-payment-qr">
- <div class="turnstile-qr-code-container"
- data-order-id="{{ order_id }}"
- data-session-id="{{ session_id }}"></div>
- <p class="turnstile-qr-help">{{ 'Scan with your GNU Taler wallet'|t }}</p>
- </div>
-
- <div class="turnstile-payment-or">
- <span>{{ 'or'|t }}</span>
- </div>
-
- <a href="{{ payment_url }}"
- class="button button--primary turnstile-pay-button"
- data-order-id="{{ order_id }}"
- data-session-id="{{ session_id }}">
- {{ 'Open GNU Taler payment Web page'|t }}
- </a>
- </div>
-
- <div class="turnstile-payment-status">
- <p class="turnstile-status-message">{{ 'Waiting for payment...'|t }}</p>
- </div>
-
-</div>
-
-<style>
-.turnstile-payment-container {
- border: 2px solid #e0e0e0;
- border-radius: 8px;
- padding: 2rem;
- margin: 2rem 0;
- background: #f9f9f9;
-}
-
-.turnstile-payment-info h3 {
- margin-top: 0;
- color: #333;
-}
-
-.turnstile-price {
- font-size: 1.2rem;
- font-weight: bold;
- color: #0066cc;
- margin: 1rem 0;
-}
-
-.turnstile-payment-actions {
- margin-top: 1.5rem;
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 1rem;
-}
-
-.turnstile-payment-qr {
- text-align: center;
- padding: 1rem;
- background: white;
- border-radius: 4px;
- border: 1px solid #ddd;
-}
-
-.turnstile-qr-code {
- display: block;
- margin: 0 auto;
- max-width: 200px;
- height: auto;
-}
-
-.turnstile-qr-help {
- margin: 0.5rem 0 0 0;
- font-size: 0.9rem;
- color: #666;
-}
-
-.turnstile-payment-or {
- margin: 0.5rem 0;
- color: #666;
- font-weight: bold;
-}
-
-.turnstile-pay-button {
- display: inline-block;
- padding: 0.75rem 2rem;
- background: #0066cc;
- color: white;
- text-decoration: none;
- border-radius: 4px;
- font-weight: bold;
- transition: background 0.3s;
-}
-
-.turnstile-pay-button:hover {
- background: #0052a3;
- color: white;
-}
-
-.turnstile-payment-status {
- margin-top: 1.5rem;
- padding: 1rem;
- background: #e3f2fd;
- border: 1px solid #90caf9;
- border-radius: 4px;
- text-align: center;
-}
-
-.turnstile-status-message {
- margin: 0;
- color: #1565c0;
- font-style: italic;
-}
-
-.turnstile-access-message {
- padding: 1rem;
- margin: 1rem 0;
- background: #fff3cd;
- border: 1px solid #ffc107;
- border-radius: 4px;
-}
-
-.turnstile-teaser-wrapper {
- position: relative;
- max-height: 400px;
- overflow: hidden;
-}
-
-.turnstile-teaser-wrapper::after {
- content: '';
- position: absolute;
- bottom: 0;
- left: 0;
- right: 0;
- height: 100px;
- background: linear-gradient(to bottom, transparent, white);
-}
-
-/* Responsive design */
-@media (min-width: 768px) {
- .turnstile-payment-actions {
- flex-direction: row;
- justify-content: center;
- align-items: center;
- }
-
- .turnstile-payment-or {
- margin: 0 1rem;
- }
-}
-</style>
diff --git a/turnstile.info.yml b/turnstile.info.yml
@@ -1,11 +0,0 @@
-name: Turnstile
-type: module
-description: 'Adds price field to nodes and requires payment for access.'
-core_version_requirement: ^9 || ^10
-package: System
-version: '0.9.0'
-dependencies:
- - drupal:node
- - drupal:field
- - drupal:user
-configure: turnstile.settings
diff --git a/turnstile.install b/turnstile.install
@@ -1,40 +0,0 @@
-<?php
-
-/**
- * @file
- * Install, update and uninstall functions for the Turnstile module.
- */
-
-use Drupal\field\Entity\FieldStorageConfig;
-use Drupal\field\Entity\FieldConfig;
-use Drupal\Core\Entity\Entity\EntityFormDisplay;
-use Drupal\Core\Entity\Entity\EntityViewDisplay;
-
-/**
- * Implements hook_install().
- */
-function turnstile_install() {
- $config = \Drupal::config('turnstile.settings');
- $enabled_types = $config->get('enabled_content_types') ?: ['article'];
-
- /** @var TurnstileFieldManager $field_manager */
- $field_manager = \Drupal::service('turnstile.field_manager');
- $field_manager->addFieldsToContentTypes($enabled_types);
-}
-
-/**
- * Implements hook_uninstall().
- */
-function turnstile_uninstall() {
- /** @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.
- \Drupal::configFactory()->getEditable('turnstile.settings')->delete();
-}
-\ No newline at end of file
diff --git a/turnstile.libraries.yml b/turnstile.libraries.yml
@@ -1,14 +0,0 @@
-payment_button:
- version: 1.x
- js:
- js/payment-button.js: {}
- dependencies:
- - core/jquery
- - core/drupal
- - core/once
- - turnstile/qrcode
-
-qrcode:
- version: 1.x
- js:
- js/qrcode.min.js: { minified: true }
diff --git a/turnstile.links.action.yml b/turnstile.links.action.yml
@@ -1,7 +0,0 @@
-# Action links for adding new price categories.
-
-entity.turnstile_price_category.add_form:
- route_name: entity.turnstile_price_category.add_form
- title: 'Add price category'
- appears_on:
- - entity.turnstile_price_category.collection
diff --git a/turnstile.links.menu.yml b/turnstile.links.menu.yml
@@ -1,20 +0,0 @@
-turnstile.settings:
- title: 'Turnstile basics'
- description: 'Configure GNU Taler payment backend and paid content types.'
- parent: system.admin_config_system
- route_name: turnstile.settings
- weight: 98
-
-turnstile.subscription_prices:
- title: 'Turnstile subscription prices'
- description: 'Configure prices for Turnstile subscriptions.'
- parent: system.admin_config_system
- route_name: turnstile.subscription_prices
- weight: 99
-
-turnstile.turnstile_price_category.collection:
- title: 'Turnstile price categories'
- route_name: entity.turnstile_price_category.collection
- description: 'Manage price categories for Turnstile.'
- parent: system.admin_structure
- weight: 10
diff --git a/turnstile.module b/turnstile.module
@@ -1,290 +0,0 @@
-<?php
-
-/**
- * @file
- * Main module file for Turnstile.
- */
-
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\EntityTypeInterface;
-use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
-use Drupal\node\NodeInterface;
-
-
-/**
- * Implements hook_form_FORM_ID_alter() for node forms. Adds a
- * description for the Turnstile price category field.
- */
-function turnstile_form_node_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
- $node = $form_state->getFormObject()->getEntity();
- $config = \Drupal::config('turnstile.settings');
- $enabled_types = $config->get('enabled_content_types') ?: [];
-
- // Only show price field on enabled content types.
- if (! in_array($node->bundle(), $enabled_types)) {
- return;
- }
- if (! isset($form['field_turnstile_price_category'])) {
- return;
- }
- $form['field_turnstile_price_category']['#group'] = 'meta';
- $form['field_turnstile_price_category']['widget'][0]['value']['#description'] = t('Set a price category to enable paywall protection for this content.');
-
- // Load all price categories for the description.
- $price_categories = \Drupal::entityTypeManager()
- ->getStorage('turnstile_price_category')
- ->loadMultiple();
-
- $category_list = [];
- foreach ($price_categories as $category) {
- $category_list[] = $category->label() . ': ' . $category->getDescription();
- }
-
- $description = t('Select a price category to enable paywall protection for this content.');
- if (!empty($category_list)) {
- $description .= '<br><br><strong>' . t('Available categories:') . '</strong><ul><li>'
- . implode('</li><li>', $category_list) . '</li></ul>';
- }
-
- $form['field_turnstile_price_category']['widget']['#description'] = $description;
-}
-
-
-/**
- * Implements hook_entity_view_alter(). Transforms the body of an entity to
- * show the Turnstile dialog instead of the full body if the user needs
- * to pay to see the full article.
- */
-function turnstile_entity_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
- // Only process nodes with turnstile enabled
- if ($entity->getEntityTypeId() !== 'node') {
- return;
- }
- /** @var \Drupal\node\NodeInterface $node */
- $node = $entity;
-
- if (!$node->hasField('field_turnstile_price_category')) {
- return;
- }
-
- /** @var \Drupal\Core\Field\EntityReferenceFieldItemList $field */
- $field = $node->get('field_turnstile_price_category');
- if ($field->isEmpty()) {
- \Drupal::logger('turnstile')->debug('No price category selected');
- return FALSE;
- }
-
- /** @var TurnstilePriceCategory $price_category */
- $price_category = $field->entity;
- if (! $price_category) {
- \Drupal::logger('turnstile')->debug('Node has no price category, skipping payment.');
- return;
- }
-
- $view_mode = $display->getMode();
- if ($view_mode !== 'full') {
- \Drupal::logger('turnstile')->debug('Turnstile only active for "Full" view mode.');
- return;
- }
-
- $subscriptions = $price_category->getFullSubscriptions();
- foreach ($subscriptions as $subscription_id) {
- if (_turnstile_is_subscriber ($subscription_id)) {
- \Drupal::logger('turnstile')->debug('Subscriber detected, granting access.');
- return;
- }
- }
-
- // Disable page cache, this page is personalized!
- \Drupal::service('page_cache_kill_switch')->trigger();
-
- $node_id = $node->id();
- if (_turnstile_has_session_access($node_id)) {
- \Drupal::logger('turnstile')->debug('Session has access to this node.');
- return;
- }
-
- /** @var \Drupal\turnstile\TalerMerchantApiService $api_service */
- $api_service = \Drupal::service('turnstile.api_service');
-
- $order_info = _turnstile_get_node_order_info ($node_id);
- if ($order_info) {
- \Drupal::logger('turnstile')->debug('Found existing order @ORDER_ID for this session.', [ '@ORDER_ID' => $order_info['order_id'] ]);
- // We have an existing order, check if it was paid
- $order_id = $order_info['order_id'];
- $order_status = $api_service->checkOrderStatus($order_info['order_id']);
- if ($order_status && $order_status['paid']) {
- \Drupal::logger('turnstile')->debug('Order was paid, granting session access.');
- _turnstile_grant_session_access($node_id);
- if ($order_status['subscription_slug'] ?? FALSE) {
- \Drupal::logger('turnstile')->debug('Subscription was purchased, granting subscription access.');
- $subscription_slug = $order_status['subscription_slug'];
- $expiration = $order_status['subscription_expiration'];
- _turnstile_grant_subscriber_access ($subscription_slug, $expiration);
- }
- return;
- }
- if ($order_status &&
- ($order_status['order_expiration'] ?? 0) < time() + 60) {
- // If order expired (or would expire in less than one minute,
- // so too soon for the user to still pay it), then ignore it!
- $order_info = NULL;
- }
- if (!$order_status)
- {
- $order_info = NULL;
- }
- else
- {
- \Drupal::logger('turnstile')->debug('Order expires in @future seconds, not creating new one.', ['@future' => ($order_status['order_expiration'] ?? 0) - time ()] );
- }
- }
- if (!$order_info) {
- // Need to try to create a new order
- $order_info = $api_service->createOrder($node);
- }
- if (!$order_info) {
- \Drupal::logger('turnstile')->warning('Failed to setup order with Taler merchant backend!');
- $config = \Drupal::config('turnstile.settings');
- $grant_access_on_error = $config->get('grant_access_on_error') ?? TRUE;
- if ($grant_access_on_error) {
- \Drupal::logger('turnstile')->debug('Could not setup order, disabling Turnstile.');
- return;
- }
- $pay_button = [
- '#markup' => '<div class="turnstile-error">' . t('Payment system temporarily unavailable. Please try again later.') . '</div>',
- ];
- }
- else
- {
- _turnstile_store_order_node_mapping($node_id, $order_info);
- $pay_button = [
- '#theme' => 'turnstile_payment_button',
- '#order_id' => $order_info['order_id'],
- '#session_id' => $order_info['session_id'],
- '#payment_url' => $order_info['payment_url'],
- '#node_title' => $node->getTitle(),
- '#attached' => [
- 'library' => ['turnstile/payment_button'],
- ],
- ];
- }
- // User needs to pay - replace full content with teaser + payment button
- // Generate teaser view mode
- $view_builder = \Drupal::entityTypeManager()->getViewBuilder('node');
- $teaser_build = $view_builder->view($entity, 'teaser');
-
- // Replace the build array with teaser content
- // Keep important metadata from original build (?)
- $build = [
- '#cache' => ['contexts' => ['url']],
- '#weight' => $build['#weight'] ?? 0,
- ];
-
- // Add teaser content
- $build['teaser'] = [
- '#type' => 'container',
- '#attributes' => ['class' => ['turnstile-teaser-wrapper']],
- 'content' => $teaser_build,
- '#weight' => 0,
- ];
-
- // Add payment button
- $build['payment_button'] = [
- '#type' => 'container',
- '#attributes' => ['class' => ['turnstile-payment-wrapper']],
- 'button' => $pay_button,
- '#weight' => 10,
- ];
-}
-
-
-/**
- * Helper function to grant subscription access for this
- * visitor to the given node ID until the given expiration time.
- */
-function _turnstile_grant_subscriber_access($subscription_slug, $expiration) {
- $session = \Drupal::request()->getSession();
- $access_data = $session->get('turnstile_subscriptions', []);
- $access_data[$subscription_slug] = $expiration;
- $session->set('turnstile_subscriptions', $access_data);
-}
-
-
-/**
- * Helper function to check if this session is currently
- * subscribed on the given type of subscription.
- */
-function _turnstile_is_subscriber($subscription_slug) {
- $session = \Drupal::request()->getSession();
- $access_data = $session->get('turnstile_subscriptions', []);
- return ($access_data[$subscription_slug] ?? 0) >= time();
-}
-
-
-/**
- * Helper function to grant session access for this
- * visitor to the given node ID.
- */
-function _turnstile_grant_session_access($node_id) {
- $session = \Drupal::request()->getSession();
- $access_data = $session->get('turnstile_access', []);
- $access_data[$node_id] = TRUE;
- $session->set('turnstile_access', $access_data);
-}
-
-
-/**
- * Helper function to check session access. Checks if this
- * visitor has been granted access to the given $node_id.
- */
-function _turnstile_has_session_access($node_id) {
- $session = \Drupal::request()->getSession();
- $access_data = $session->get('turnstile_access', []);
- return $access_data[$node_id] ?? FALSE;
-}
-
-
-/**
- * Store the mapping between order_id and node_id.
- * Uses session to track which orders belong to which nodes.
- */
-function _turnstile_store_order_node_mapping($node_id, $order_info) {
- $session = \Drupal::request()->getSession();
- $node_orders = $session->get('turnstile_node_orders', []);
- $node_orders[$node_id] = $order_info;
- $session->set('turnstile_node_orders', $node_orders);
-}
-
-
-/**
- * Get the order_info associated with a node_id.
- */
-function _turnstile_get_node_order_info($node_id) {
- $session = \Drupal::request()->getSession();
- $node_orders = $session->get('turnstile_node_orders', []);
- return $node_orders[$node_id] ?? NULL;
-}
-
-
-/**
- * Implements hook_theme().
- */
-function turnstile_theme() {
- return [
- 'turnstile_payment_button' => [
- 'variables' => [
- 'order_id' => NULL,
- 'session_id' => NULL,
- 'payment_url' => NULL,
- 'node_title' => NULL,
- ],
- 'template' => 'turnstile-payment-button',
- ],
- 'turnstile_settings' => [
- 'variables' => [
- 'config' => NULL,
- ],
- ],
- ];
-}
diff --git a/turnstile.permissions.yml b/turnstile.permissions.yml
@@ -1,9 +0,0 @@
-administer turnstile:
- title: 'Administer Turnstile'
- description: 'Configure Turnstile settings and manage payment options.'
- restrict access: true
-
-administer price categories:
- title: 'Administer price categories'
- description: 'Create, edit, and delete price categories.'
- restrict access: true
diff --git a/turnstile.routing.yml b/turnstile.routing.yml
@@ -1,53 +0,0 @@
-turnstile.settings:
- path: '/admin/config/system/Turnstile'
- defaults:
- _form: '\Drupal\turnstile\Form\TurnstileSettingsForm'
- _title: 'Turnstile settings'
- requirements:
- _permission: 'administer Turnstile'
- options:
- _admin_route: TRUE
-
-# Route for editing subscription prices
-turnstile.subscription_prices:
- path: '/admin/config/system/turnstile/subscription-prices'
- defaults:
- _form: '\Drupal\turnstile\Form\SubscriptionPricesForm'
- _title: 'Subscription prices'
- requirements:
- _permission: 'administer Turnstile'
- options:
- _admin_route: TRUE
-
-# Routes for price categories.
-entity.turnstile_price_category.collection:
- path: '/admin/structure/price-categories'
- defaults:
- _entity_list: 'turnstile_price_category'
- _title: 'Price categories'
- requirements:
- _permission: 'administer price categories'
-
-entity.turnstile_price_category.add_form:
- path: '/admin/structure/price-categories/add'
- defaults:
- _entity_form: 'turnstile_price_category.add'
- _title: 'Add price category'
- requirements:
- _permission: 'administer price categories'
-
-entity.turnstile_price_category.edit_form:
- path: '/admin/structure/price-categories/{turnstile_price_category}/edit'
- defaults:
- _entity_form: 'turnstile_price_category.edit'
- _title: 'Edit price category'
- requirements:
- _permission: 'administer price categories'
-
-entity.turnstile_price_category.delete_form:
- path: '/admin/structure/price-categories/{turnstile_price_category}/delete'
- defaults:
- _entity_form: 'turnstile_price_category.delete'
- _title: 'Delete price category'
- requirements:
- _permission: 'administer price categories'
diff --git a/turnstile.services.yml b/turnstile.services.yml
@@ -1,12 +0,0 @@
-services:
- turnstile.api_service:
- 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']