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 }