association-1.0.0-alpha2/src/Behavior/Form/ConfigureManifestBehaviorForm.php

src/Behavior/Form/ConfigureManifestBehaviorForm.php
<?php

namespace Drupal\association\Behavior\Form;

use Drupal\association\Entity\AssociationTypeInterface;
use Drupal\association\EntityAdapterManagerInterface;
use Drupal\association\Plugin\AssociationPluginFormInterface;
use Drupal\association\Plugin\BehaviorInterface;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\SortArray;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\RemoveCommand;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Form\SubformStateInterface;
use Drupal\Core\Plugin\PluginFormBase;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\toolshed\Strategy\Exception\StrategyException;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Plugin form for editing the EntityManifestBehavior configurations.
 */
class ConfigureManifestBehaviorForm extends PluginFormBase implements AssociationPluginFormInterface, ContainerInjectionInterface {

  use StringTranslationTrait;
  use DependencySerializationTrait;

  /**
   * The plugin being configured by this plugin form.
   *
   * @var \Drupal\association\Plugin\BehaviorInterface
   */
  protected $plugin;

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * Association entity adapter plugin manager.
   *
   * @var \Drupal\association\EntityAdapterManagerInterface
   */
  protected $adapterManager;

  /**
   * The form element to use for multi-select.
   *
   * @var string
   */
  protected $multiselectType = 'select';

  /**
   * The entity bundle options that can be used for the tag allowed bundles.
   *
   * Array of values meant to be used as available options for a select form
   * element, when building out the association link tag definitions.
   *
   * @var \Drupal\Core\StringTranslation\TranslatableMarkup[]|string[]
   */
  protected $entityBundleOpts;

  /**
   * Create a new instance of the ConfigureManifestBehaviorPluginForm form.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Drupal\association\EntityAdapterManagerInterface $entity_adapter_manager
   *   Association entity adapter plugin manager.
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityAdapterManagerInterface $entity_adapter_manager) {
    $this->entityTypeManager = $entity_type_manager;
    $this->adapterManager = $entity_adapter_manager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    $instance = new static(
      $container->get('entity_type.manager'),
      $container->get('plugin.manager.association.entity_adapter')
    );

    // Prefer select2 if it is available. Otherwise Chosen and Core select
    // element both work using "select" as the element type.
    $moduleHandler = $container->get('module_handler');
    if ($moduleHandler->moduleExists('select2')) {
      $instance->setMultiselectType('select2');
    }
    elseif ($moduleHandler->moduleExists('a11y_autocomplete_element')) {
      $instance->setMultiselectType('a11y_autocomplete');
    }

    return $instance;
  }

  /**
   * Set the form element type to use for multiple select.
   *
   * Use "select2" if the select2 module is enabled. The "select" type works
   * for the Drupal core and chosenjs .
   *
   * @param string $select_type
   *   The type of select element type to use when creating multi-select form
   *   elements for behavior configurations.
   */
  public function setMultiselectType($select_type) {
    $this->multiselectType = $select_type;
  }

  /**
   * Callback function to check if entity association link tag already exists.
   *
   * @param string $tag
   *   The suggested machine name to use for this association link tag.
   * @param array $element
   *   The machine name form element.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current form state, build info and values.
   *
   * @return bool
   *   Returns TRUE if this tag is already being used in this entity manifest,
   *   or will return FALSE otherwise.
   */
  public static function linkTagExists($tag, array $element, FormStateInterface $form_state): bool {
    $parents = array_slice($element['#parents'], 0, -2);
    $values = $form_state->getValue($parents);

    return isset($values['manifest'][$tag]);
  }

  /**
   * Get the entity and bundle combinations that can be used in a manifest.
   *
   * @return array
   *   The entity and bundle options that can be used in the select options
   *   for the tags.
   */
  protected function getEntityBundleOptions(): array {
    if (!isset($this->entityBundleOpts)) {
      $this->entityBundleOpts = [];

      foreach ($this->adapterManager->getEntityTypes() as $entityTypeId) {
        try {
          if ($adapter = $this->adapterManager->getAdapterByEntityType($entityTypeId)) {
            $entityTypeLabel = (string) $adapter->getLabel();

            foreach ($adapter->getBundles() as $bundle => $bundleLabel) {
              $this->entityBundleOpts[$entityTypeLabel]["$entityTypeId:$bundle"] = $bundleLabel;
            }
          }
        }
        catch (StrategyException $e) {
          // Issue retrieving or loading the entity adapter plugin, skip this
          // type as the adapter provider might be missing or uninstalled.
        }
      }
    }

    return $this->entityBundleOpts;
  }

  /**
   * {@inheritdoc}
   */
  public function buildConfigurationForm(array $elements, FormStateInterface $form_state, AssociationTypeInterface $association_type = NULL): array {
    $form_state->set('association_type', $association_type->id());

    $hasData = $association_type->hasData();
    $configuration = $form_state->getValues();

    if (!$configuration) {
      $configuration = $this->plugin->getConfiguration();

      // Convert from the plugin configuration format to one easier to
      // manipulate and represent with form elements.
      foreach ($configuration['manifest'] ?? [] as &$tagDef) {
        $entityBundles = [];

        foreach ($tagDef['entity_types'] ?? [] as $type => $bundles) {
          foreach ($bundles as $bundle) {
            $entityBundles["$type:$bundle"] = "$type:$bundle";
          }
        }
        $tagDef['entity_types'] = $entityBundles;
      }
      unset($tagDef);
    }

    $elements['#id'] = 'association-entity-manifest-behavior-configuration';

    // Create the table management for the manifests table.
    $elements['manifest'] = [
      '#type' => 'table',
      '#tree' => TRUE,
      '#empty' => $this->t('No entity types have been configured yet. Add one below.'),
      '#header' => [
        'label' => $this->t('Label'),
        'entity_info' => $this->t('Allowed bundle'),
        'limit' => $this->t('Limit'),
        'weight' => $this->t('Sort order'),
      ],
      '#tabledrag' => [
        'sort' => [
          'action' => 'order',
          'relationship' => 'sibling',
          'group' => 'entity_tag__weight',
        ],
      ],
    ];

    if (!$hasData) {
      // Operations are enabled when there is existing data.
      $elements['manifest']['#header']['op'] = $this->t('Actions');
    }

    foreach ($configuration['manifest'] ?? [] as $tag => $tagDef) {
      $elements['manifest'][$tag] = $this->buildEntityRow($tag, $tagDef, $hasData);
    }

    $elements['__add_tag'] = [
      '#type' => 'details',
      '#title' => $this->t('Add entity definition'),
      '#open' => TRUE,
      '#process' => [static::class . '::addTagElementsProcess'],

      'label' => [
        '#type' => 'textfield',
        '#title' => $this->t('Label'),
        '#size' => 32,
        '#maxlength' => 32,
        '#default_value' => '',
      ],
      'tag' => [
        '#type' => 'machine_name',
        '#title' => $this->t('Machine name'),
        '#required' => FALSE,
        '#size' => 20,
        '#maxlength' => 32,
        '#machine_name' => [
          'exists' => static::class . '::linkTagExists',
        ],
      ],
      'entity_types' => [
        '#type' => $this->multiselectType,
        '#title' => $this->t('Allowed entity types'),
        '#options' => $this->getEntityBundleOptions(),
        '#multiple' => TRUE,
      ],
      'add_submit' => [
        '#type' => 'submit',
        '#value' => $this->t('Add'),
        '#ajax' => [
          'wrapper' => $elements['#id'],
          'callback' => static::class . '::addTagAjax',
        ],
        '#validate' => [static::class . '::addTagValidate'],
        '#submit' => [static::class . '::addTagSubmit'],
      ],
    ];

    return $elements;
  }

  /**
   * Convert an entry of entity manifest into a table row configuration.
   *
   * @param string $tag
   *   The identifier for the entity entry for this manifest object.
   * @param array $entry
   *   The relatable entity definition from the manifest.
   * @param bool $has_data
   *   Does the association type (bundle) already have data?
   *
   * @return array
   *   A table row of elements for the relatable entity tags.
   */
  protected function buildEntityRow($tag, array $entry, $has_data) {
    $range = range(1, 10);
    $range = array_combine($range, $range);

    $rowId = 'manifest-behavior-row-id-' . Html::cleanCssIdentifier($tag);
    $row = [
      '#attributes' => [
        'id' => $rowId,
        'class' => ['draggable'],
      ],
      'label' => [
        '#type' => 'textfield',
        '#default_value' => $entry['label'],
      ],
      'entity_types' => [
        '#type' => $this->multiselectType,
        '#required' => TRUE,
        '#multiple' => TRUE,
        '#options' => $this->getEntityBundleOptions(),
        '#default_value' => $entry['entity_types'],
      ],
      'limit' => [
        '#type' => 'select',
        '#disabled' => $has_data,
        '#options' => [
          BehaviorInterface::CARDINALITY_UNLIMITED => $this->t('Unlimited'),
        ] + $range,
        '#default_value' => $entry['limit'],
      ],
      'weight' => [
        '#type' => 'number',
        '#default_value' => $entry['weight'],
        '#attributes' => ['class' => ['entity_tag__weight']],
      ],
    ];

    if (!$has_data) {
      $row['actions'] = [
        '#type' => 'submit',
        '#value' => $this->t('Remove'),
        '#access' => !$has_data,
        '#name' => $rowId . '-remove-op',
        '#tagId' => $tag,
        '#validate' => [],
        '#submit' => [static::class . '::removeTagSubmit'],
        '#ajax' => [
          'wrapper' => $rowId,
          'callback' => static::class . '::removeTagAjax',
        ],
      ];
    }

    return $row;
  }

  /**
   * Add element adjust parents and validation scope of the add entity elements.
   *
   * At the original time of processing, the #array_parents and #parents
   * information is not available yet. Since we can't assuming the nesting
   * depth of these form elements, ee need wait for the form processing step
   * of the form lifecycle before making these adjustments.
   *
   * @param array $element
   *   The add new entity elements to update.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state, build and values information.
   * @param array $complete_form
   *   Reference to the entire form elements.
   *
   * @return array
   *   Modified form elements.
   */
  public static function addTagElementsProcess(array $element, FormStateInterface $form_state, array &$complete_form) {
    // At the time of form build, the #array_parents aren't available yet,
    // and we won't know the depth that the plugin form is being embedded into,
    // so we update the "source" in as the form element is built.
    $element['tag']['#machine_name']['source'] = array_merge($element['#array_parents'], ['label']);

    // Limit the validation errors to only the add submit values, and the
    // current definition manifest values.
    $valParents = $element['#parents'];
    array_pop($valParents);
    $element['add_submit']['#limit_validation_errors'][] = array_merge($valParents, ['__add_tag']);
    $element['add_submit']['#limit_validation_errors'][] = array_merge($valParents, ['manifest']);

    return $element;
  }

  /**
   * Form validation callback to ensure requirements for a new entity entry.
   *
   * @param array $form
   *   Reference to the complete form structure and elements.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state, build info and values.
   */
  public static function addTagValidate(array &$form, FormStateInterface $form_state) {
    $trigger = $form_state->getTriggeringElement();
    $parents = array_slice($trigger['#parents'], 0, -1);
    $values = $form_state instanceof SubformStateInterface
      ? $form_state->getValue('__add_tag') : $form_state->getValue($parents);

    $elements = NestedArray::getValue($form, array_slice($trigger['#array_parents'], 0, -1));
    if (empty($values['tag'])) {
      $form_state->setError($elements['tag'], t('An idenfifier is required to add a new entity association.'));
    }

    if (empty($values['entity_types'])) {
      $form_state->setError($elements['entity_types'], t('Entity types selections are required.'));
    }
  }

  /**
   * Form submit callback to add a relatable entity type to the manifest.
   *
   * @param array $form
   *   Reference to the entire form structure for the edit form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current form state, build and values.
   */
  public static function addTagSubmit(array &$form, FormStateInterface $form_state) {
    $trigger = $form_state->getTriggeringElement();
    $parents = array_slice($trigger['#parents'], 0, -2);

    $form_state = $form_state instanceof SubformStateInterface ? $form_state->getCompleteFormState() : $form_state;
    $values = $form_state->getValue($parents);
    $addValues = $values['__add_tag'];

    // Reset the values, so the prefilled don't get repopulated.
    $addTagParents = array_merge($parents, ['__add_tag']);
    NestedArray::unsetValue($form_state->getUserInput(), $addTagParents);
    unset($values['__add_tag']);

    // Make sure that the current manifest setting is an array
    // and not a string or NULL.
    if (!(isset($values['manifest']) && is_array($values['manifest']))) {
      $values['manifest'] = [];
    }

    // Transfer the newly added entity into the manifest list.
    $values['manifest'][$addValues['tag']] = [
      'label' => $addValues['label'],
      'entity_types' => $addValues['entity_types'],
      'required' => 0,
      'limit' => 1,
      'weight' => 10,
    ];

    $form_state->setValue($parents, $values);
    $form_state->setRebuild(TRUE);
  }

  /**
   * AJAX callback to update the association entity manifest.
   *
   * @param array $form
   *   Reference to the entire form structure for the edit form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current form state, build and values.
   *
   * @return array
   *   Renderable array with the manifest table updated elements.
   */
  public static function addTagAjax(array &$form, FormStateInterface $form_state) {
    $element = $form_state->getTriggeringElement();
    $parents = array_slice($element['#array_parents'], 0, -2);

    // Return the whole configuration table without the external wrappers.
    $render = NestedArray::getValue($form, $parents);
    unset($render['#prefix'], $render['#suffix']);

    return $render;
  }

  /**
   * Form submit callback to remove a association entity from the manifest.
   *
   * @param array $form
   *   Reference to the entire form structure for the edit form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current form state, build and values.
   */
  public static function removeTagSubmit(array &$form, FormStateInterface $form_state) {
    $trigger = $form_state->getTriggeringElement();
    $parents = array_slice($trigger['#parents'], 0, -2);

    // Ensure that we are working with the complete form_state.
    $form_state = $form_state instanceof SubformStateInterface ? $form_state->getCompleteFormState() : $form_state;

    // Remove the entity from the manifest table.
    $values = $form_state->getValue($parents);
    unset($values[$trigger['#tagId']]);

    $form_state->setValue($parents, $values);
    $form_state->setRebuild(TRUE);
  }

  /**
   * AJAX callback to update the association entity manifest after a removal.
   *
   * @param array $form
   *   Reference to the entire form structure for the edit form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current form state, build and values.
   *
   * @return \Drupal\Core\Ajax\AjaxResponse
   *   An AJAX response which updates after the removal of a relatable entity.
   */
  public static function removeTagAjax(array &$form, FormStateInterface $form_state) {
    $trigger = $form_state->getTriggeringElement();
    $rowId = preg_replace('#-remove-op$#', '', $trigger['#name']);

    $response = new AjaxResponse();

    if ($rowId) {
      $response->addCommand(new RemoveCommand("#{$rowId}"));
    }

    return $response;
  }

  /**
   * {@inheritdoc}
   */
  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
    // @todo Ensure that the validation for manifest prevents the addition
    // of configurations that could cause data inconsistencies.
    $trigger = $form_state->getTriggeringElement();
    $parents = array_slice($trigger['#parents'], 0, -2);

    $values = $form_state instanceof SubformStateInterface
      ? $form_state->getValues() : $form_state->getValue($parents);

    if (empty($values['manifest']) || !is_array($values['manifest'])) {
      $form_state->setErrorByName(implode('][', $parents), $this->t('Entity manigest cannot be empty.'));
    }
  }

  /**
   * {@inheritdoc}
   */
  public function submitConfigurationForm(array &$element, FormStateInterface $form_state) {
    $trigger = $form_state->getTriggeringElement();
    $parents = array_slice($trigger['#parents'], 0, -2);

    $values = $form_state instanceof SubformStateInterface
      ? $form_state->getValues() : $form_state->getValue($parents);

    if (empty($values['manifest']) || !is_array($values['manifest'])) {
      $values['manifest'] = [];
    }

    // Reorder the manifest based on the configured weights of the entries.
    uasort($values['manifest'], SortArray::class . '::sortByWeightElement');

    // Renumber these weights so it'll be easier to manage in the future.
    $weight = 0;
    foreach ($values['manifest'] as &$entry) {
      $entry['required'] = 0;
      $entry['weight'] = $weight++;

      $entityTypes = [];
      foreach ($entry['entity_types'] as $def) {
        [$type, $bundle] = explode(':', $def, 2);
        $entityTypes[$type][$bundle] = $bundle;
      }
      $entry['entity_types'] = $entityTypes;
    }

    $this->plugin->setConfiguration([
      'manifest' => $values['manifest'],
    ]);
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc