paragraphs_sets-8.x-2.x-dev/src/ParagraphsSets.php

src/ParagraphsSets.php
<?php

namespace Drupal\paragraphs_sets;

use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\TranslatableInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\paragraphs\Plugin\Field\FieldWidget\ParagraphsWidget;

/**
 * Utitlity class for paragraphs_sets.
 */
class ParagraphsSets {

  // Define the default "empty value" for Paragraphs Sets.
  const PARAGRAPHS_SETS_DEFAULT_EMPTY_VALUE = '_none';

  /**
   * Get a list of all available sets.
   *
   * @param array<string> $allowed_paragraphs_types
   *   Optional list of allowed paragraphs types.
   *
   * @return array<int, array<string, array<string, string>>>
   *   List of all Paragraphs Sets.
   */
  public static function getSets(array $allowed_paragraphs_types = []): array {
    $query = \Drupal::entityQuery('paragraphs_set');
    $config_factory = \Drupal::configFactory();
    /** @var array<int> $results */
    $results = $query->execute();
    $sets = [];
    foreach ($results as $id) {
      /** @var \Drupal\Core\Config\ImmutableConfig $config */
      $config = $config_factory->get("paragraphs_sets.set.{$id}");
      /** @var array{
       *   'paragraphs': array{
       *     'bundle': string,
       *     ...
       *   },
       *   ...
       * } $data */
      $data = $config->get();
      // Check that all paragraph types in set are allowed in field.
      if (!empty($allowed_paragraphs_types)) {
        $types_filtered = array_intersect(array_column($data['paragraphs'], 'bundle'), $allowed_paragraphs_types);
        if (count($types_filtered) === count($data['paragraphs'])) {
          $sets[$id] = $data;
        }
      }
    }

    return $sets;
  }

  /**
   * Get an array of id => label of available sets.
   *
   * @param array<string> $allowed_paragraphs_types
   *   List of allowed paragraph types.
   * @param int $cardinality
   *   Number of items allowed.
   *
   * @return array<int, string>
   *   Sets labels, keyed by id.
   */
  public static function getSetsOptions(array $allowed_paragraphs_types = [], $cardinality = FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED): array {
    $sets_data = static::getSets($allowed_paragraphs_types);
    $opts = [];
    foreach ($sets_data as $k => $set) {
      /** @var array{
       *   'label': string,
       *   'paragraphs': array<mixed>,
       *   ...
       * } $set */
      if (($cardinality !== FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) && (count($set['paragraphs']) > $cardinality)) {
        // Do not add sets having more paragraphs than allowed.
        continue;
      }
      $opts[$k] = $set['label'];
    }

    return $opts;
  }

  /**
   * Builds select element for set selection.
   *
   * @param array $elements
   *   Form elements to build the selection for.
   * @param array $context
   *   Required context for the set selection.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   * @param string|null $default
   *   Current selected set.
   *
   * @return array<mixed>
   *   The form element array.
   */
  public static function buildSelectSetSelection(array $elements, array $context, FormStateInterface $form_state, $default = NULL): array {
    /** @var \Drupal\paragraphs\Plugin\Field\FieldWidget\ParagraphsWidget $widget */
    $widget = $context['widget'];
    if (!($widget instanceof ParagraphsWidget) || empty($context['widget'])) {
      return [];
    }
    $settings = $widget->getThirdPartySettings('paragraphs_sets');

    $items = $context['items'];
    $field_definition = $items->getFieldDefinition();
    $field_name = $field_definition->getName();
    $title = $field_definition->getLabel();
    $cardinality = $field_definition->getFieldStorageDefinition()->getCardinality();
    $field_parents = $context['form']['#parents'];
    $field_id_prefix = implode('-', array_merge($field_parents, [$field_name]));
    $field_wrapper_id = Html::getId($field_id_prefix . '-add-more-wrapper');
    $field_state = static::getWidgetState($field_parents, $field_name, $form_state);

    // Get a list of all Paragraphs types allowed in this field.
    $field_allowed_paragraphs_types = $widget->getAllowedTypes($field_definition);
    $options = static::getSetsOptions(array_keys($field_allowed_paragraphs_types), $cardinality);
    // Further limit sets available from widget settings.
    if (isset($settings['paragraphs_sets']['sets_allowed']) && count(array_filter($settings['paragraphs_sets']['sets_allowed']))) {
      $options = array_intersect_key($options, array_filter($settings['paragraphs_sets']['sets_allowed']));
    }

    $options = [
      '_none' => t('- None -'),
      ] + $options;

    $selection_elements = [
      '#type' => 'container',
      '#theme_wrappers' => ['container'],
      '#attributes' => [
        'class' => ['set-selection-wrapper-element'],
      ],
    ];
    $selection_elements['set_selection_select'] = [
      '#type' => 'select',
      '#options' => $options,
      '#default_value' => $default,
      '#title' => t('@title set', ['@title' => $widget->getSetting('title')]),
      '#label_display' => 'hidden',
    ];

    $selection_elements['set_selection_button'] = [
      '#type' => 'submit',
      '#name' => strtr($field_id_prefix, '-', '_') . '_set_selection',
      '#value' => t('Select set'),
      '#attributes' => ['class' => ['field-set-selection-submit']],
      '#limit_validation_errors' => [
        array_merge($field_parents, [$field_name, 'set_selection']),
      ],
      '#submit' => [['\Drupal\paragraphs_sets\ParagraphsSets', 'setSetSubmit']],
      '#ajax' => [
        'callback' => ['\Drupal\paragraphs_sets\ParagraphsSets', 'setSetAjax'],
        'wrapper' => $field_wrapper_id,
        'effect' => 'fade',
      ],
    ];
    $selection_elements['set_selection_button']['#prefix'] = '<div class="paragraphs-set-button paragraphs-set-button-set">';
    $selection_elements['set_selection_button']['#suffix'] = t('for %type', ['%type' => $title]) . '</div>';

    if ($field_state['items_count'] && ($field_state['items_count'] < $cardinality || $cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) && !$form_state->isProgrammed() && (isset($elements['#allow_reference_changes']) && $elements['#allow_reference_changes'])) {
      $selection_elements['append_selection_button'] = [
        '#type' => 'submit',
        '#name' => strtr($field_id_prefix, '-', '_') . '_append_selection',
        '#value' => t('Append set'),
        '#attributes' => ['class' => ['field-append-selection-submit']],
        '#limit_validation_errors' => [
          array_merge($field_parents, [$field_name, 'append_selection']),
        ],
        '#submit' => [
          ['\Drupal\paragraphs_sets\ParagraphsSets', 'setSetSubmit'],
        ],
        '#ajax' => [
          'callback' => ['\Drupal\paragraphs_sets\ParagraphsSets', 'setSetAjax'],
          'wrapper' => $field_wrapper_id,
          'effect' => 'fade',
        ],
      ];
      $selection_elements['append_selection_button']['#prefix'] = '<div class="paragraphs-set-button paragraphs-set-button-append">';
      $selection_elements['append_selection_button']['#suffix'] = t('to %type', ['%type' => $title]) . '</div>';
    }

    return $selection_elements;
  }

  /**
   * Retrieves processing information about the widget from $form_state.
   *
   * This method is static so that it can be used in static Form API callbacks.
   *
   * @param array $parents
   *   The array of #parents where the field lives in the form.
   * @param string $field_name
   *   The field name.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   *
   * @return array<mixed>
   *   An array with the following key/value pairs:
   *   - items_count: The number of widgets to display for the field.
   *   - array_parents: The location of the field's widgets within the $form
   *     structure. This entry is populated at '#after_build' time.
   */
  public static function getWidgetState(array $parents, $field_name, FormStateInterface $form_state): array {
    $storage = $form_state->getStorage();
    /** @var array<mixed> $return */
    $return = NestedArray::getValue($storage, static::getWidgetStateParents($parents, $field_name));

    return $return;
  }

  /**
   * Stores processing information about the widget in $form_state.
   *
   * This method is static so that it can be used in static Form API #callbacks.
   *
   * @param array $parents
   *   The array of #parents where the widget lives in the form.
   * @param string $field_name
   *   The field name.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   * @param array $field_state
   *   The array of data to store. See getWidgetState() for the structure and
   *   content of the array.
   */
  public static function setWidgetState(array $parents, $field_name, FormStateInterface $form_state, array $field_state): void {
    $storage = &$form_state->getStorage();
    NestedArray::setValue($storage, static::getWidgetStateParents($parents, $field_name), $field_state);
  }

  /**
   * Returns the location of processing information within $form_state.
   *
   * @param array $parents
   *   The array of #parents where the widget lives in the form.
   * @param string $field_name
   *   The field name.
   *
   * @return array<string>
   *   The location of processing information within $form_state.
   */
  public static function getWidgetStateParents(array $parents, $field_name): array {
    // Field processing data is placed at
    // $form_state->get(['field_storage', '#parents', ...$parents..., '#fields',
    // $field_name]), to avoid clashes between field names and $parents parts.
    return array_merge(
      ['field_storage', '#parents'],
      $parents,
      ['#fields', $field_name]
    );
  }

  /**
   * Get the element for ajax operations.
   *
   * @param array $form
   *   The form render array.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current form stage.
   *
   * @return array<mixed>
   */
  public static function setSetAjax(array $form, FormStateInterface $form_state): array {
    /** @var array{
     *   '#array_parents': array<int, string>,
     *   '#set_machine_name'?: string,
     *   ...
     * } $button */
    $button = $form_state->getTriggeringElement();
    // Go one level up in the form, to the widgets container.
    /** @var array<mixed> $element */
    $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -2));

    return $element;
  }

  /**
   * {@inheritdoc}
   */
  public static function setSetSubmit(array $form, FormStateInterface $form_state): void {
    /** @var array{
     *   '#array_parents': array<int, string>,
     *   '#set_machine_name'?: string,
     *   ...
     * } $button */
    $button = $form_state->getTriggeringElement();
    $array_parents = $button['#array_parents'];

    // Go one level up in the form, to the widgets container.
    $element = NestedArray::getValue($form, array_slice($array_parents, 0, -2));
    if (!is_array($element)) {
      throw new \RuntimeException('Element must be an array.');
    }

    /** @var array{
     *   '#field_name': string,
     *   '#field_parents': array<int, string>,
     *   set_selection?: array{
     *     set_selection_select: array{
     *       '#value': string,
     *       ...
     *     },
     *     ...
     *   },
     *   ...
     * } $element */
    $field_name = $element['#field_name'];
    $parents = $element['#field_parents'];

    // Get the last element from array_parents.
    $button_type = $array_parents[array_key_last($array_parents)];

    // Increment the items count.
    $widget_state = static::getWidgetState($parents, $field_name, $form_state);
    $widget_state['button_type'] = $button_type;

    if (isset($button['#set_machine_name'])) {
      /** @var string $machine_name */
      $machine_name = $button['#set_machine_name'];
      $widget_state['selected_set'] = $machine_name;
    }
    else if (isset($element['set_selection'])) {
      $widget_state['selected_set'] = $element['set_selection']['set_selection_select']['#value'];
    }

    static::setWidgetState($parents, $field_name, $form_state, $widget_state);

    $form_state->setRebuild();
  }

  /**
   * Prepares the widget state to add a new paragraph at a specific position.
   *
   * In addition to the widget state change, also user input could be modified
   * to handle adding of a new paragraph at a specific position between existing
   * paragraphs.
   *
   * @param array $widget_state
   *   Widget state as reference, so that it can be updated.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   Form state.
   * @param array $field_path
   *   Path to paragraph field.
   * @param int|mixed $new_delta
   *   Delta position in list of paragraphs, where new paragraph will be added.
   */
  public static function prepareDeltaPosition(array &$widget_state, FormStateInterface $form_state, array $field_path, $new_delta): void {
    // Increase number of items to create place for new paragraph.
    $widget_state['items_count']++;

    // Default behavior is adding to end of list and in case delta is not
    // provided or already at end, we can skip all other steps.
    if (!is_numeric($new_delta) || intval($new_delta) >= $widget_state['real_item_count']) {
      return;
    }

    $widget_state['real_item_count']++;

    // Limit delta between 0 and "number of items" in paragraphs widget.
    $new_delta = max(intval($new_delta), 0);

    // Change user input in order to create new delta position.
    $form_state_user_input = &$form_state->getUserInput();
    /** @var array<int, array<string, int|string>> $user_input */
    $user_input = NestedArray::getValue($form_state_user_input, $field_path);

    // Rearrange all original deltas to make one place for the new element.
    $new_original_deltas = [];
    foreach ($widget_state['original_deltas'] as $current_delta => $original_delta) {
      $new_current_delta = $current_delta >= $new_delta ? $current_delta + 1 : $current_delta;

      $new_original_deltas[$new_current_delta] = $original_delta;
      $user_input[$original_delta]['_weight'] = $new_current_delta;
    }

    // Add information into delta mapping for the new element.
    $original_deltas_size = count($widget_state['original_deltas']);
    $new_original_deltas[$new_delta] = $original_deltas_size;
    $user_input[$original_deltas_size]['_weight'] = $new_delta;

    $widget_state['original_deltas'] = $new_original_deltas;
    NestedArray::setValue($form_state_user_input, $field_path, $user_input);
  }

  /**
   * Check if form state is in translation.
   *
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   Form state.
   * @param \Drupal\Core\Entity\EntityInterface $host
   *   The host entity.
   *
   * @return bool
   *   TRUE if the form state is in translation mode, FALSE otherwise.
   */
  public static function inTranslation(FormStateInterface $form_state, EntityInterface $host) {
    $is_in_translation = FALSE;

    assert($host instanceof TranslatableInterface);
    assert($host instanceof ContentEntityInterface);

    if (!$host->isTranslatable()) {
      return FALSE;
    }
    if (!$host->getEntityType()->hasKey('default_langcode')) {
      return FALSE;
    }

    /** @var string $default_langcode_key */
    $default_langcode_key = $host->getEntityType()->getKey('default_langcode');
    if (!$host->hasField($default_langcode_key)) {
      return FALSE;
    }

    if (!empty($form_state->get('content_translation'))) {
      // Adding a language through the ContentTranslationController.
      $is_in_translation = TRUE;
    }

    /** @var string $langcode_current */
    $langcode_current = $form_state->get('langcode');
    if ($host->hasTranslation($langcode_current) && $host->getTranslation($langcode_current)->get($default_langcode_key)->value == 0) {
      // Editing a translation.
      $is_in_translation = TRUE;
    }

    return $is_in_translation;
  }

}

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

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