Unverified Commit bcf6c4a0 authored by Alex Pott's avatar Alex Pott
Browse files

Revert "Issue #3483435 by phenaproxima, alexpott, thejimbirch: Add a trait for...

Revert "Issue #3483435 by phenaproxima, alexpott, thejimbirch: Add a trait for forms that want to collect input on behalf of a recipe"

This reverts commit 45843c37.
parent 45843c37
Loading
Loading
Loading
Loading
Loading
+6 −6
Original line number Diff line number Diff line
@@ -72,7 +72,7 @@ public function __construct(
        $definition['constraints'],
      );
      $data_definition->setSettings($definition);
      $this->data[$name] = $typedDataManager->create($data_definition, name: "$prefix.$name");
      $this->data[$name] = $typedDataManager->create($data_definition);
    }
  }

@@ -112,9 +112,9 @@ public function describeAll(): array {
    foreach ($this->dependencies->recipes as $dependency) {
      $descriptions = array_merge($descriptions, $dependency->input->describeAll());
    }
    foreach ($this->data as $data) {
      $name = $data->getName();
      $descriptions[$name] = $data->getDataDefinition()->getDescription();
    foreach ($this->getDataDefinitions() as $key => $definition) {
      $name = $this->prefix . '.' . $key;
      $descriptions[$name] = $definition->getDescription();
    }
    return $descriptions;
  }
@@ -153,7 +153,7 @@ public function collectAll(InputCollectorInterface $collector, array &$processed
      $definition = $data->getDataDefinition();

      $value = $collector->collectValue(
        $data->getName(),
        $this->prefix . '.' . $key,
        $definition,
        $this->getDefaultValue($definition),
      );
@@ -161,7 +161,7 @@ public function collectAll(InputCollectorInterface $collector, array &$processed

      $violations = $data->validate();
      if (count($violations) > 0) {
        throw new ValidationFailedException($data, $violations);
        throw new ValidationFailedException($value, $violations);
      }
      $this->values[$key] = $data->getCastedValue();
    }
+2 −16
Original line number Diff line number Diff line
@@ -9,7 +9,6 @@
use Drupal\Core\Extension\ModuleExtensionList;
use Drupal\Core\Extension\ThemeExtensionList;
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Render\Element;
use Drupal\Core\TypedData\PrimitiveInterface;
use Drupal\Core\Validation\Plugin\Validation\Constraint\RegexConstraint;
use Symfony\Component\Validator\Constraints\All;
@@ -204,8 +203,8 @@ private static function parse(string $file): array {
                  'interface' => PrimitiveInterface::class,
                ]),
              ],
              // The `prompt` and `form` elements, though optional, have their
              // own sets of constraints,
              // If there is a `prompt` element, it has its own set of
              // constraints.
              'prompt' => new Optional([
                new Collection([
                  'method' => [
@@ -216,19 +215,6 @@ private static function parse(string $file): array {
                  ]),
                ]),
              ]),
              'form' => new Optional([
                new Sequentially([
                  new Type('associative_array'),
                  // Every element in the `form` array has to be a form API
                  // property, prefixed with `#`. Because recipe inputs can only
                  // be primitive data types, child elements aren't allowed.
                  new Callback(function (array $element, ExecutionContextInterface $context): void {
                    if (Element::children($element)) {
                      $context->addViolation('Form elements for recipe inputs cannot have child elements.');
                    }
                  }),
                ]),
              ]),
              // Every input must define a default value.
              'default' => new Required([
                new Collection([
+0 −151
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Core\Recipe;

use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\TypedData\DataDefinitionInterface;
use Drupal\Core\TypedData\TypedDataInterface;
use Symfony\Component\Validator\Exception\ValidationFailedException;

/**
 * Defines helper methods for forms which collect input on behalf of recipes.
 */
trait RecipeInputFormTrait {

  /**
   * Generates a tree of form elements for a recipe's inputs.
   *
   * @param \Drupal\Core\Recipe\Recipe $recipe
   *   A recipe.
   *
   * @return array[]
   *   A nested array of form elements for collecting input values for the given
   *   recipe and its dependencies. The elements will be grouped by the recipe
   *   that defined the input -- for example, $return['recipe_name']['input1'],
   *   $return['recipe_name']['input2'], $return['dependency']['input_name'],
   *   and so forth. The returned array will have the `#tree` property set to
   *   TRUE.
   */
  protected function buildRecipeInputForm(Recipe $recipe): array {
    $collector = new class () implements InputCollectorInterface {

      /**
       * A form array containing the input elements for the given recipe.
       *
       * This will be a tree of input elements, grouped by the name of the
       * recipe that defines them. For example:
       *
       * @code
       * $form = [
       *   'recipe_1' => [
       *     'input_1' => [
       *       '#type' => 'textfield',
       *       '#title' => 'Some input value',
       *     ],
       *     'input_2' => [
       *       '#type' => 'checkbox',
       *       '#title' => 'Enable some feature or other?',
       *     ],
       *   ],
       *   'dependency_recipe' => [
       *     'input_1' => [
       *       '#type' => 'textarea',
       *       '#title' => 'An input defined by a dependency of recipe_1',
       *     ],
       *   ],
       *   '#tree' => TRUE,
       * ];
       * @endcode
       *
       * The `#tree` property will always be set to TRUE.
       *
       * @var array
       */
      // phpcs:ignore DrupalPractice.CodeAnalysis.VariableAnalysis.UnusedVariable
      public array $form = [];

      /**
       * {@inheritdoc}
       */
      public function collectValue(string $name, DataDefinitionInterface $definition, mixed $default_value): mixed {
        $element = $definition->getSetting('form');
        if ($element) {
          $element += [
            '#description' => $definition->getDescription(),
            '#default_value' => $default_value,
          ];
          // Recipe inputs are always required.
          $element['#required'] = TRUE;
          NestedArray::setValue($this->form, explode('.', $name, 2), $element);

          // Always return the input elements as a tree.
          $this->form['#tree'] = TRUE;
        }
        return $default_value;
      }

    };
    $recipe->input->collectAll($collector);
    return $collector->form;
  }

  /**
   * Validates user-inputted values to a recipe and its dependencies.
   *
   * @param \Drupal\Core\Recipe\Recipe $recipe
   *   A recipe.
   * @param array $form
   *   The form being validated, which should include the tree of elements
   *   returned by ::buildRecipeInputForm().
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current form state. The values should be organized in the tree
   *   structure that was returned by ::buildRecipeInputForm().
   */
  protected function validateRecipeInput(Recipe $recipe, array &$form, FormStateInterface $form_state): void {
    try {
      $this->setRecipeInput($recipe, $form_state);
    }
    catch (ValidationFailedException $e) {
      $data = $e->getValue();

      if ($data instanceof TypedDataInterface) {
        $element = NestedArray::getValue($form, explode('.', $data->getName(), 2));
        $form_state->setError($element, $e->getMessage());
      }
      else {
        // If the data isn't a typed data object, we have no idea how to handle
        // the situation, so just re-throw the exception.
        throw $e;
      }
    }
  }

  /**
   * Supplies user-inputted values to a recipe and its dependencies.
   *
   * @param \Drupal\Core\Recipe\Recipe $recipe
   *   A recipe.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current form state. The values should be organized in the tree
   *   structure that was returned by ::buildRecipeInputForm().
   */
  protected function setRecipeInput(Recipe $recipe, FormStateInterface $form_state): void {
    $recipe->input->collectAll(new class ($form_state) implements InputCollectorInterface {

      public function __construct(private readonly FormStateInterface $formState) {
      }

      /**
       * {@inheritdoc}
       */
      public function collectValue(string $name, DataDefinitionInterface $definition, mixed $default_value): mixed {
        return $this->formState->getValue(explode('.', $name, 2), $default_value);
      }

    });
  }

}
+0 −7
Original line number Diff line number Diff line
@@ -564,10 +564,3 @@ form_test.incorrect_config_target:
    _admin_route: TRUE
  requirements:
    _access: 'TRUE'

form_test.recipe_input:
  path: '/form-test/recipe-input'
  defaults:
    _form: '\Drupal\form_test\Form\FormTestRecipeInputForm'
  requirements:
    _access: 'TRUE'
+0 −63
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\form_test\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Recipe\Recipe;
use Drupal\Core\Recipe\RecipeInputFormTrait;
use Drupal\Core\Recipe\RecipeRunner;

class FormTestRecipeInputForm extends FormBase {

  use RecipeInputFormTrait;

  /**
   * {@inheritdoc}
   */
  public function getFormId(): string {
    return 'form_test_recipe_input';
  }

  /**
   * Returns the recipe object under test.
   *
   * @return \Drupal\Core\Recipe\Recipe
   *   A Recipe object for the input_test recipe.
   */
  private function getRecipe(): Recipe {
    return Recipe::createFromDirectory('core/tests/fixtures/recipes/input_test');
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state): array {
    $form += $this->buildRecipeInputForm($this->getRecipe());

    $form['apply'] = [
      '#type' => 'submit',
      '#value' => $this->t('Apply recipe'),
    ];
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state): void {
    $this->validateRecipeInput($this->getRecipe(), $form, $form_state);
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state): void {
    $recipe = $this->getRecipe();
    $this->setRecipeInput($recipe, $form_state);
    RecipeRunner::processRecipe($recipe);
  }

}
Loading