turnstile

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

TurnstileConfigSubscriber.php (7458B)


      1 <?php
      2 
      3 /**
      4  * @file
      5  * Location: src/EventSubscriber/TurnstileConfigSubscriber.php
      6  *
      7  * Subscriber for turnstile config changes.
      8  */
      9 
     10 namespace Drupal\taler_turnstile\EventSubscriber;
     11 
     12 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
     13 use Drupal\Core\StringTranslation\StringTranslationTrait;
     14 use Drupal\Core\StringTranslation\TranslationInterface;
     15 use Drupal\Core\Config\ConfigCrudEvent;
     16 use Drupal\Core\Config\ConfigEvents;
     17 use Drupal\Core\Entity\EntityTypeManagerInterface;
     18 use Drupal\taler_turnstile\TurnstileFieldManager;
     19 use Drupal\taler_turnstile\TalerMerchantApiService;
     20 use Drupal\Core\Messenger\MessengerInterface;
     21 
     22 class TurnstileConfigSubscriber implements EventSubscriberInterface {
     23 
     24   // This provides the $this->t() method.
     25   use StringTranslationTrait;
     26 
     27   /**
     28    * The entity type manager.
     29    *
     30    * @var \Drupal\Core\Entity\EntityTypeManagerInterface
     31    */
     32   protected $entityTypeManager;
     33 
     34   /**
     35    * The Turnstile field manager.
     36    *
     37    * @var \Drupal\taler_turnstile\TurnstileFieldManager
     38    */
     39   protected $fieldManager;
     40 
     41 
     42   /**
     43    * The messenger service.
     44    *
     45    * @var \Drupal\Core\Messenger\MessengerInterface
     46    */
     47   protected $messenger;
     48 
     49   /**
     50    * The Turnstile API service.
     51    *
     52    * @var \Drupal\taler_turnstile\TalerMerchantApiService
     53    */
     54   protected $apiService;
     55 
     56   /**
     57    * Constructs a TurnstileSettingsForm object.
     58    *
     59    * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
     60    *   The translation interface.
     61    * @param \Drupal\Core\Messenger\MessengerInterface
     62    *   The messenger interface.
     63    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
     64    *   The entity type manager.
     65    * @param \Drupal\taler_turnstile\TurnstileFieldManager $field_manager
     66    *   The field manager.
     67    * @param \Drupal\taler_turnstile\TalerMerchantApiService $api_service
     68    *   The Turnstile API service.
     69    */
     70   public function __construct(TranslationInterface $string_translation,
     71                               MessengerInterface $messenger,
     72                               EntityTypeManagerInterface $entity_type_manager,
     73                               TurnstileFieldManager $field_manager,
     74                               TalerMerchantApiService $api_service) {
     75     $this->stringTranslation = $string_translation;
     76     $this->messenger = $messenger;
     77     $this->entityTypeManager = $entity_type_manager;
     78     $this->fieldManager = $field_manager;
     79     $this->apiService = $api_service;
     80   }
     81 
     82   /**
     83    * {@inheritdoc}
     84    */
     85   public static function getSubscribedEvents() {
     86     // Listen for the configuration save event.
     87     $events[ConfigEvents::SAVE][] = ['onConfigSave'];
     88     return $events;
     89   }
     90 
     91   /**
     92    * React to configuration changes.
     93    *
     94    * @param \Drupal\Core\Config\ConfigCrudEvent $event
     95    * The configuration event.
     96    */
     97   public function onConfigSave(ConfigCrudEvent $event) {
     98     $config = $event->getConfig();
     99     if ($config->getName() !== 'taler_turnstile.settings') {
    100       return;
    101     }
    102     if ($event->isChanged('enabled_content_types')) {
    103       $old_enabled_types = $config->getOriginal('enabled_content_types') ?? [];
    104       $new_enabled_types = $config->get('enabled_content_types');
    105 
    106       // FIXME: Consider validating new_enabled_types.
    107 
    108       // Find content types to add and remove.
    109       $types_to_add = array_diff($new_enabled_types, $old_enabled_types);
    110       $types_to_remove = array_diff($old_enabled_types, $new_enabled_types);
    111 
    112       // Add fields to newly enabled content types.
    113       if (!empty($types_to_add)) {
    114         $this->fieldManager->addFieldsToContentTypes($types_to_add);
    115       }
    116 
    117       // Remove fields from disabled content types.
    118       if (!empty($types_to_remove)) {
    119         $this->fieldManager->removeFieldsFromContentTypes($types_to_remove);
    120       }
    121       if (empty($new_enabled_types)) {
    122         $this->fieldManager->cleanupFieldStorage();
    123       }
    124       // Display summary of changes.
    125       if (!empty($types_to_add) || !empty($types_to_remove)) {
    126         $this->displayFieldChanges($types_to_add, $types_to_remove);
    127       }
    128       \Drupal::logger('taler_turnstile')->info('Turnstile content types changed from @old to @new.', [
    129         '@old' => json_encode($old_enabled_types),
    130         '@new' => json_encode($new_enabled_types),
    131       ]);
    132     }
    133     // Subscription prices feed every category's "buy subscription"
    134     // choices, so keep all merchant templates in sync when they
    135     // change. Also re-push everything when the operator (re)configures
    136     // the backend or rotates the access token: the new instance will
    137     // not yet have any of our templates.
    138     $needs_sync = $event->isChanged('subscription_prices')
    139       || $event->isChanged('payment_backend_url')
    140       || $event->isChanged('access_token');
    141     if ($needs_sync) {
    142       $results = $this->apiService->syncAllTemplates();
    143       $this->reportSyncResults($results, (string) ($config->get('payment_backend_url') ?? ''));
    144     }
    145   }
    146 
    147   /**
    148    * Report the outcome of syncAllTemplates() to the admin via the
    149    * messenger. Categories that hit NOT_CONFIGURED are ignored: this
    150    * is the expected state when the operator deliberately clears the
    151    * backend URL.
    152    *
    153    * @param array<string, \Drupal\taler_turnstile\TalerBackendResult> $results
    154    *   Map of price-category id => sync result.
    155    * @param string $backend_url
    156    *   Backend URL the calls were made against (for the error message).
    157    */
    158   protected function reportSyncResults(array $results, string $backend_url): void {
    159     $failed = [];
    160     foreach ($results as $id => $r) {
    161       if (!$r->isOk()
    162           && $r->kind !== \Drupal\taler_turnstile\TalerBackendErrorKind::NOT_CONFIGURED) {
    163         $failed[$id] = $r;
    164       }
    165     }
    166     if (empty($failed)) {
    167       return;
    168     }
    169     $first = reset($failed);
    170     $this->messenger->addError($this->t(
    171       'Settings saved, but @n of @t price-category templates failed to publish to the merchant backend. First failure (@id): @err',
    172       [
    173         '@n'   => count($failed),
    174         '@t'   => count($results),
    175         '@id'  => array_key_first($failed),
    176         '@err' => $first->toUserMessage($backend_url),
    177       ]
    178     ));
    179   }
    180 
    181   /**
    182    * Display messages about field changes.
    183    *
    184    * @param array $types_added
    185    *   Content types that had fields added.
    186    * @param array $types_removed
    187    *   Content types that had fields removed.
    188    */
    189   protected function displayFieldChanges(array $types_added, array $types_removed) {
    190     $content_types = $this->entityTypeManager->getStorage('node_type')->loadMultiple();
    191 
    192     if (!empty($types_added)) {
    193       $added_labels = [];
    194       foreach ($types_added as $type) {
    195         if (isset($content_types[$type])) {
    196           $added_labels[] = $content_types[$type]->label();
    197         }
    198       }
    199       if (!empty($added_labels)) {
    200         $this->messenger->addStatus(
    201           $this->t('Price category fields added to: @types', [
    202             '@types' => implode(', ', $added_labels),
    203           ])
    204         );
    205       }
    206     }
    207 
    208     if (!empty($types_removed)) {
    209       $removed_labels = [];
    210       foreach ($types_removed as $type) {
    211         if (isset($content_types[$type])) {
    212           $removed_labels[] = $content_types[$type]->label();
    213         }
    214       }
    215       if (!empty($removed_labels)) {
    216         $this->messenger->addStatus(
    217           $this->t('Price category fields removed from: @types', [
    218             '@types' => implode(', ', $removed_labels),
    219           ])
    220         );
    221       }
    222     }
    223   }
    224 }