Repository navigation
312: Added OS2Forms handler interface for validating handler configur… #340
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
902f14f
8edf122
6033007
cee7fb5
ef2eb4c
146da06
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| services: | ||
| Drupal\os2forms\Helper\HandlerConfigurationValidator: | ||
| arguments: | ||
| - "@form_builder" | ||
| - "@messenger" | ||
|
|
||
| Drupal\os2forms\EventSubscriber\HandlerConfigurationValidationSubscriber: | ||
| arguments: | ||
| - "@Drupal\\os2forms\\Helper\\HandlerConfigurationValidator" | ||
| - "@current_route_match" | ||
| - "@messenger" | ||
| tags: | ||
| - { name: event_subscriber } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| <?php | ||
|
|
||
| namespace Drupal\os2forms\EventSubscriber; | ||
|
|
||
| use Drupal\Core\Messenger\MessengerInterface; | ||
| use Drupal\Core\Routing\RouteMatchInterface; | ||
| use Drupal\Core\StringTranslation\StringTranslationTrait; | ||
| use Drupal\os2forms\Helper\HandlerConfigurationValidator; | ||
| use Drupal\webform\WebformInterface; | ||
| use Symfony\Component\EventDispatcher\EventSubscriberInterface; | ||
| use Symfony\Component\HttpKernel\Event\RequestEvent; | ||
| use Symfony\Component\HttpKernel\KernelEvents; | ||
|
|
||
| /** | ||
| * Reports misconfigured webform handlers on webform admin pages. | ||
| * | ||
| * @see \Drupal\os2forms\Helper\HandlerConfigurationValidator | ||
| */ | ||
| class HandlerConfigurationValidationSubscriber implements EventSubscriberInterface { | ||
| use StringTranslationTrait; | ||
|
|
||
| /** | ||
| * Routes on which to report misconfigured handlers. | ||
| */ | ||
| private const ROUTE_NAMES = [ | ||
| // The webform build page (where elements are renamed or deleted). | ||
| 'entity.webform.edit_form', | ||
| // The webform handlers page. | ||
| 'entity.webform.handlers', | ||
| ]; | ||
|
|
||
| /** | ||
| * Constructs the HandlerConfigurationValidationSubscriber. | ||
| * | ||
| * @param \Drupal\os2forms\Helper\HandlerConfigurationValidator $validator | ||
| * The handler configuration validator. | ||
| * @param \Drupal\Core\Routing\RouteMatchInterface $routeMatch | ||
| * The current route match. | ||
| * @param \Drupal\Core\Messenger\MessengerInterface $messenger | ||
| * The messenger. | ||
| */ | ||
| public function __construct( | ||
| private readonly HandlerConfigurationValidator $validator, | ||
| private readonly RouteMatchInterface $routeMatch, | ||
| private readonly MessengerInterface $messenger, | ||
| ) { | ||
| } | ||
|
|
||
| /** | ||
| * Reports misconfigured webform handlers to the current user. | ||
|
jekuaitk marked this conversation as resolved.
Resolved
|
||
| * | ||
| * @param \Symfony\Component\HttpKernel\Event\RequestEvent $event | ||
| * The request event. | ||
| */ | ||
| public function onRequest(RequestEvent $event): void { | ||
| if (!in_array($this->routeMatch->getRouteName(), self::ROUTE_NAMES, TRUE)) { | ||
| return; | ||
| } | ||
|
|
||
| // Only report on regular page views. | ||
| $request = $event->getRequest(); | ||
| if (!$request->isMethod('GET') || $request->isXmlHttpRequest()) { | ||
| return; | ||
| } | ||
|
|
||
| $webform = $this->routeMatch->getParameter('webform'); | ||
| if (!$webform instanceof WebformInterface) { | ||
| return; | ||
| } | ||
|
|
||
| foreach ($this->validator->validate($webform) as $handlerId => $result) { | ||
| foreach ($result['problems'] as $problem) { | ||
| $this->messenger->addWarning($this->t('The handler @handler (@id) is misconfigured: @problem', [ | ||
|
cableman marked this conversation as resolved.
Resolved
|
||
| '@handler' => $result['label'], | ||
| '@id' => $handlerId, | ||
| '@problem' => $problem, | ||
| ])); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritdoc} | ||
| * | ||
| * @phpstan-return array<string, mixed> | ||
| */ | ||
| public static function getSubscribedEvents(): array { | ||
| return [ | ||
| KernelEvents::REQUEST => ['onRequest'], | ||
| ]; | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| <?php | ||
|
|
||
| namespace Drupal\os2forms\Form; | ||
|
|
||
| use Drupal\Core\Form\FormInterface; | ||
| use Drupal\Core\Form\FormStateInterface; | ||
| use Drupal\Core\Form\SubformState; | ||
| use Drupal\webform\Plugin\WebformHandlerInterface; | ||
|
|
||
| /** | ||
| * Form used for programmatically validating webform handler configurations. | ||
| * | ||
| * Building the form populates all elements with their default values, i.e. | ||
| * the stored handler configuration, and submitting the form programmatically | ||
| * with no input validates the configuration against the current webform | ||
| * state (cf. \Drupal\Core\Form\FormBuilder::submitForm()). | ||
| * | ||
| * The real handler edit form cannot be used for this as submitting | ||
| * \Drupal\webform\Form\WebformHandlerFormBase updates and saves the webform. | ||
| * | ||
| * @see \Drupal\os2forms\Helper\HandlerConfigurationValidator | ||
| */ | ||
| class HandlerConfigurationValidationForm implements FormInterface { | ||
|
|
||
| /** | ||
| * Constructs a HandlerConfigurationValidationForm. | ||
| * | ||
| * @param \Drupal\webform\Plugin\WebformHandlerInterface $handler | ||
| * The webform handler whose configuration to validate. | ||
| */ | ||
| public function __construct( | ||
| private readonly WebformHandlerInterface $handler, | ||
| ) { | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritdoc} | ||
| */ | ||
| public function getFormId() { | ||
| return 'os2forms_handler_configuration_validation_form'; | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritdoc} | ||
| * | ||
| * @phpstan-param array<string, mixed> $form | ||
| * @phpstan-return array<string, mixed> | ||
| */ | ||
| public function buildForm(array $form, FormStateInterface $form_state) { | ||
| // Build the handler configuration form the same way as | ||
| // \Drupal\webform\Form\WebformHandlerFormBase::form(). | ||
| $form['#parents'] = []; | ||
| $form['settings'] = [ | ||
| '#tree' => TRUE, | ||
| '#parents' => ['settings'], | ||
| ]; | ||
| $subform_state = SubformState::createForSubform($form['settings'], $form, $form_state); | ||
| $form['settings'] = $this->handler->buildConfigurationForm($form['settings'], $subform_state); | ||
| $form['settings']['#tree'] = TRUE; | ||
|
|
||
| return $form; | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritdoc} | ||
| * | ||
| * @phpstan-param array<string, mixed> $form | ||
| */ | ||
| public function validateForm(array &$form, FormStateInterface $form_state) { | ||
| $subform_state = SubformState::createForSubform($form['settings'], $form, $form_state); | ||
| // Pass the complete form as | ||
| // \Drupal\webform\Form\WebformHandlerFormBase::validateForm() does. | ||
| // Handlers are written against that contract and may index the full form. | ||
| $this->handler->validateConfigurationForm($form, $subform_state); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since we are working with
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some handlers index specific values in the entire form, i.e. |
||
| } | ||
|
|
||
| /** | ||
| * {@inheritdoc} | ||
| * | ||
| * @phpstan-param array<string, mixed> $form | ||
| */ | ||
| public function submitForm(array &$form, FormStateInterface $form_state) { | ||
| // Intentionally empty: this form is only used for validation. | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| <?php | ||
|
|
||
| namespace Drupal\os2forms\Helper; | ||
|
|
||
| use Drupal\Core\Form\FormBuilderInterface; | ||
| use Drupal\Core\Form\FormState; | ||
| use Drupal\Core\Messenger\MessengerInterface; | ||
| use Drupal\Core\StringTranslation\StringTranslationTrait; | ||
| use Drupal\os2forms\Form\HandlerConfigurationValidationForm; | ||
| use Drupal\webform\Plugin\WebformHandlerInterface; | ||
| use Drupal\webform\WebformInterface; | ||
|
|
||
| /** | ||
| * Validates configuration of webform handlers. | ||
| */ | ||
| class HandlerConfigurationValidator { | ||
| use StringTranslationTrait; | ||
|
|
||
| /** | ||
| * Constructs a HandlerConfigurationValidator object. | ||
| * | ||
| * @param \Drupal\Core\Form\FormBuilderInterface $formBuilder | ||
| * The form builder. | ||
| * @param \Drupal\Core\Messenger\MessengerInterface $messenger | ||
| * The messenger. | ||
| */ | ||
| public function __construct( | ||
| private readonly FormBuilderInterface $formBuilder, | ||
| private readonly MessengerInterface $messenger, | ||
| ) { | ||
| } | ||
|
|
||
| /** | ||
| * Validates configuration of all handlers on a webform. | ||
| * | ||
| * Handlers are validated by programmatically submitting their | ||
| * configuration form with the stored configuration. | ||
| * | ||
| * @param \Drupal\webform\WebformInterface $webform | ||
| * The webform. | ||
| * | ||
| * @return array | ||
| * Problems keyed by handler ID. Each value is an array with two keys: | ||
| * - label: the webform handler label | ||
| * - problems: a list of problems (strings or markup) | ||
| * Handlers without problems are not included. | ||
| * | ||
| * @phpstan-return array<string, mixed> | ||
| */ | ||
| public function validate(WebformInterface $webform): array { | ||
| $problems = []; | ||
| foreach ($webform->getHandlers() as $handler) { | ||
| $handlerProblems = $this->validateUsingConfigurationForm($handler); | ||
| if ($handlerProblems) { | ||
| $problems[$handler->getHandlerId()] = [ | ||
| 'label' => $handler->label(), | ||
| 'problems' => $handlerProblems, | ||
| ]; | ||
| } | ||
| } | ||
|
|
||
| return $problems; | ||
| } | ||
|
|
||
| /** | ||
| * Validates handler configuration using the handler's configuration form. | ||
| * | ||
| * The configuration form is submitted programmatically with no input; form | ||
| * elements then use their default values, i.e. the stored handler | ||
| * configuration, and form validation reports stored values that are no | ||
| * longer valid on the current webform, e.g. references to elements that | ||
| * have been renamed or deleted (reported as illegal choices). | ||
| * | ||
| * @param \Drupal\webform\Plugin\WebformHandlerInterface $handler | ||
| * The webform handler. | ||
| * | ||
| * @return array | ||
| * A list of problems (strings or markup). An empty list means that the | ||
| * configuration is valid. | ||
| * | ||
| * @phpstan-return array<int, mixed> | ||
| */ | ||
| private function validateUsingConfigurationForm(WebformHandlerInterface $handler): array { | ||
| $formState = new FormState(); | ||
| // Building and validating the form may add messages, e.g. warnings from | ||
| // the handler's own validation, that must not leak to the current page. | ||
| // Remember any existing messages and restore them when done. | ||
| $messages = $this->messenger->deleteAll(); | ||
| try { | ||
| $this->formBuilder->submitForm(new HandlerConfigurationValidationForm($handler), $formState); | ||
| $errors = $formState->getErrors(); | ||
| } | ||
| catch (\Throwable $throwable) { | ||
| $errors = [ | ||
| $this->t('Validating the configuration failed: @message', [ | ||
| '@message' => $throwable->getMessage(), | ||
| ]), | ||
| ]; | ||
| } | ||
| finally { | ||
| $this->messenger->deleteAll(); | ||
| foreach ($messages as $type => $typeMessages) { | ||
| foreach ($typeMessages as $message) { | ||
| $this->messenger->addMessage($message, $type, TRUE); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return array_values($errors); | ||
| } | ||
|
|
||
| } |
Uh oh!
There was an error while loading. Please reload this page.