You are here

protected static function ParagraphsWidget::reorderParagraphs in Paragraphs 8

Reorder paragraphs.

Parameters

\Drupal\Core\Form\FormStateInterface $form_state: The form state.

$field_values_parents: The field value parents.

1 call to ParagraphsWidget::reorderParagraphs()
ParagraphsWidget::massageFormValues in src/Plugin/Field/FieldWidget/ParagraphsWidget.php
Massages the form values into the format expected for field values.

File

src/Plugin/Field/FieldWidget/ParagraphsWidget.php, line 1930

Class

ParagraphsWidget
Plugin implementation of the 'entity_reference_revisions paragraphs' widget.

Namespace

Drupal\paragraphs\Plugin\Field\FieldWidget

Code

protected static function reorderParagraphs(FormStateInterface $form_state, $field_values_parents) {
  $field_name = end($field_values_parents);
  $field_values = NestedArray::getValue($form_state
    ->getValues(), $field_values_parents);
  $complete_field_storage = NestedArray::getValue($form_state
    ->getStorage(), [
    'field_storage',
    '#parents',
  ]);
  $new_field_storage = $complete_field_storage;

  // Set a flag to prevent this from running twice, as the entity is built
  // for validation as well as saving and would fail the second time as we
  // already altered the field storage.
  if (!empty($new_field_storage['#fields'][$field_name]['reordered'])) {
    return;
  }
  $new_field_storage['#fields'][$field_name]['reordered'] = TRUE;

  // Clear out all current paragraphs keys in all nested paragraph widgets
  // as there might be fewer than before or none in a certain widget.
  $clear_paragraphs = function ($field_storage) use (&$clear_paragraphs) {
    foreach ($field_storage as $key => $value) {
      if ($key === '#fields') {
        foreach ($value as $field_name => $widget_state) {
          if (isset($widget_state['paragraphs'])) {
            $field_storage['#fields'][$field_name]['paragraphs'] = [];
          }
        }
      }
      else {
        $field_storage[$key] = $clear_paragraphs($field_storage[$key]);
      }
    }
    return $field_storage;
  };

  // Only clear the current field and its children to avoid deleting
  // paragraph references in other fields.
  $new_field_storage['#fields'][$field_name]['paragraphs'] = [];
  if (isset($new_field_storage[$field_name])) {
    $new_field_storage[$field_name] = $clear_paragraphs($new_field_storage[$field_name]);
  }
  $reorder_paragraphs = function ($reorder_values, $parents = [], FieldableEntityInterface $parent_entity = NULL) use ($complete_field_storage, &$new_field_storage, &$reorder_paragraphs) {
    foreach ($reorder_values as $field_name => $values) {
      foreach ($values['list'] as $delta => $item_values) {
        $old_keys = array_merge($parents, [
          '#fields',
          $field_name,
          'paragraphs',
          $delta,
        ]);
        $path = explode('][', $item_values['_path']);
        $new_field_name = array_pop($path);
        $key_parents = [];
        foreach ($path as $i => $key) {
          $key_parents[] = $key;
          if ($i % 2 == 1) {
            $key_parents[] = 'subform';
          }
        }
        $new_keys = array_merge($key_parents, [
          '#fields',
          $new_field_name,
          'paragraphs',
          $item_values['_weight'],
        ]);
        $key_exists = NULL;
        $item_state = NestedArray::getValue($complete_field_storage, $old_keys, $key_exists);
        if (!$key_exists && $parent_entity) {

          // If key does not exist, then this parent widget was previously
          // not expanded. This can only happen on nested levels. In that
          // case, initialize a new item state and set the widget state to
          // an empty array if it is not already set from an earlier item.
          // If something else is placed there, it will be put in there,
          // otherwise the widget will know that nothing is there anymore.
          $item_state = [
            'entity' => $parent_entity
              ->get($field_name)
              ->get($delta)->entity,
            'mode' => 'closed',
          ];
          $widget_state_keys = array_slice($old_keys, 0, count($old_keys) - 2);
          if (!NestedArray::getValue($new_field_storage, $widget_state_keys)) {
            NestedArray::setValue($new_field_storage, $widget_state_keys, [
              'paragraphs' => [],
            ]);
          }
        }

        // Ensure the referenced paragraph will be saved.
        $item_state['entity']
          ->setNeedsSave(TRUE);
        NestedArray::setValue($new_field_storage, $new_keys, $item_state);
        if (isset($item_values['dragdrop'])) {

          // If there is no field storage yet for the new position, initialize
          // it to an empty array in case all paragraphs have been moved away
          // from it.
          foreach (array_keys($item_values['dragdrop']) as $sub_field_name) {
            $new_widget_state_keys = array_merge($parents, [
              $field_name,
              $item_values['_weight'],
              'subform',
              '#fields',
              $sub_field_name,
            ]);
            if (!NestedArray::getValue($new_field_storage, $new_widget_state_keys)) {
              NestedArray::setValue($new_field_storage, $new_widget_state_keys, [
                'paragraphs' => [],
              ]);
            }
          }
          $reorder_paragraphs($item_values['dragdrop'], array_merge($parents, [
            $field_name,
            $delta,
            'subform',
          ]), $item_state['entity']);
        }
      }
    }
  };
  $reorder_paragraphs($field_values['dragdrop']);

  // Recalculate original deltas.
  $recalculate_original_deltas = function ($field_storage, ContentEntityInterface $parent_entity) use (&$recalculate_original_deltas) {
    if (isset($field_storage['#fields'])) {
      foreach ($field_storage['#fields'] as $field_name => $widget_state) {
        if (isset($widget_state['paragraphs'])) {

          // If the parent field does not exist but we have paragraphs in
          // widget state, something went wrong and we have a mismatch.
          // Throw an exception.
          if (!$parent_entity
            ->hasField($field_name) && !empty($widget_state['paragraphs'])) {
            throw new \LogicException('Reordering paragraphs resulted in paragraphs on non-existing field ' . $field_name . ' on parent entity ' . $parent_entity
              ->getEntityTypeId() . '/' . $parent_entity
              ->id());
          }

          // Sort the paragraphs by key so that they will be assigned to
          // the entity in the right order. Reset the deltas.
          ksort($widget_state['paragraphs']);
          $widget_state['paragraphs'] = array_values($widget_state['paragraphs']);
          $original_deltas = range(0, count($widget_state['paragraphs']) - 1);
          $field_storage['#fields'][$field_name]['original_deltas'] = $original_deltas;
          $field_storage['#fields'][$field_name]['items_count'] = count($widget_state['paragraphs']);
          $field_storage['#fields'][$field_name]['real_item_count'] = count($widget_state['paragraphs']);

          // Update the parent entity and point to the new children, if the
          // parent field does not exist, we also have no paragraphs, so
          // we can just skip this, this is a dead leaf after re-ordering.
          // @todo Clean this up somehow?
          if ($parent_entity
            ->hasField($field_name)) {
            $parent_entity
              ->set($field_name, array_column($widget_state['paragraphs'], 'entity'));

            // Next process that field recursively.
            foreach (array_keys($widget_state['paragraphs']) as $delta) {
              if (isset($field_storage[$field_name][$delta]['subform'])) {
                $field_storage[$field_name][$delta]['subform'] = $recalculate_original_deltas($field_storage[$field_name][$delta]['subform'], $parent_entity
                  ->get($field_name)
                  ->get($delta)->entity);
              }
            }
          }
        }
      }
    }
    return $field_storage;
  };
  $parent_entity = $form_state
    ->getFormObject()
    ->getEntity();
  $new_field_storage = $recalculate_original_deltas($new_field_storage, $parent_entity);
  $form_state
    ->set([
    'field_storage',
    '#parents',
  ], $new_field_storage);
}