layout_paragraphs-1.0.x-dev/layout_paragraphs.module

layout_paragraphs.module
<?php

/**
 * @file
 * Contains layout_paragraphs.module.
 */

use Drupal\Core\Url;
use Drupal\Component\Utility\Html;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Form\FormStateInterface;
use Drupal\paragraphs\ParagraphInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\layout_paragraphs\Utility\Dialog;
use Drupal\paragraphs\Entity\ParagraphsType;
use Drupal\Core\Entity\FieldableEntityInterface;

/**
 * Implements hook_help().
 */
function layout_paragraphs_help($route_name, RouteMatchInterface $route_match) {
  $output = '';
  switch ($route_name) {
    // Main module help for the layout_paragraphs module.
    case 'help.page.layout_paragraphs':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('Provide layout integration for paragraph fields.') . '</p>';
      break;
  }
  return $output;
}

/**
 * Implements hook_theme().
 */
function layout_paragraphs_theme() {
  return [
    'layout_paragraphs' => [
      'variables' => [
        'elements' => '',
        'content' => '',
      ],
    ],
    'layout_paragraphs_builder' => [
      'variables' => [
        'attributes' => [],
        'id' => '',
        'root_components' => [],
        'is_empty' => FALSE,
        'empty_message' => '',
        'insert_button' => '',
        'translation_warning' => '',
        'layout_paragraphs_layout' => NULL,
      ],
    ],
    'layout_paragraphs_builder_formatter' => [
      'variables' => [
        'link_url' => NULL,
        'link_text' => NULL,
        'field_label' => NULL,
        'is_empty' => FALSE,
        'root_components' => [],
      ],
    ],
    'layout_paragraphs_builder_controls' => [
      'variables' => [
        'attributes' => [],
        'controls' => [],
        'layout_paragraphs_layout' => NULL,
        'uuid' => NULL,
        'edit_access' => FALSE,
        'duplicate_access' => FALSE,
        'delete_access' => FALSE,
      ],
    ],
    'layout_paragraphs_insert_component_btn' => [
      'variables' => [
        'title' => NULL,
        'weight' => NULL,
        'attributes' => [],
        'url' => NULL,
      ],
    ],
    'layout_paragraphs_builder_component_menu' => [
      'variables' => [
        'attributes' => [],
        'types' => NULL,
        'empty_message' => NULL,
        'status_messages' => NULL,
      ],
    ],
  ];
}

/**
 * Implements hook_theme_suggestions().
 */
function layout_paragraphs_theme_suggestions_layout_paragraphs(array $variables) {
  $suggestions = [];
  $sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_');
  $suggestions[] = 'layout_paragraphs__' . $sanitized_view_mode;
  $suggestions[] = 'layout_paragraphs__' . $variables['elements']['field_name'];
  $suggestions[] = 'layout_paragraphs__' . $variables['elements']['field_name'] . '__' . $sanitized_view_mode;
  return $suggestions;
}

/**
 * Implements hook_module_implements_alter().
 *
 * If "content_translation", move the form_alter implementation by the
 * layout_paragraphs at the end of the list, so that it might be
 * called after the content_translation one.
 * Otherwise the $form['translatable'] won't be defined in
 * layout_paragraphs_form_field_config_edit_form_alter.
 *
 * @see: https://www.hashbangcode.com/article/drupal-8-altering-hook-weights.
 */
function layout_paragraphs_module_implements_alter(&$implementations, $hook) {
  // Move our hook_entity_type_alter() implementation to the end of the list.
  if ($hook == 'form_alter' && isset($implementations['layout_paragraphs']) && isset($implementations['content_translation'])) {
    $hook_init = $implementations['layout_paragraphs'];
    unset($implementations['layout_paragraphs']);
    $implementations['layout_paragraphs'] = $hook_init;
  }
}

/**
 * Implements hook_preprocess_radios().
 *
 * Add wrapper class for layout selection.
 */
function layout_paragraphs_preprocess_radios(&$variables) {
  if (isset($variables['element']['#wrapper_attributes'])) {
    $variables['attributes'] += $variables['element']['#wrapper_attributes'];
  }
}

/**
 * Implements hook_preprocess_layout_paragraphs_builder_controls().
 *
 * @todo Consider adding an alter/info hook for altering this output.
 */
function layout_paragraphs_preprocess_layout_paragraphs_builder_controls(&$variables) {
  static $show_layout_plugin_labels = NULL;
  if ($show_layout_plugin_labels === NULL) {
    $settings = \Drupal::config('layout_paragraphs.settings');
    $show_layout_plugin_labels = \Drupal::config('layout_paragraphs.settings')
      ->get('show_layout_plugin_labels');
  }
  /** @var \Drupal\layout_paragraphs\LayoutParagraphsLayout $layout */
  $layout = $variables['layout_paragraphs_layout'];
  $uuid = $variables['uuid'];
  $component = $layout->getComponentByUuid($uuid);
  $entity = $component->getEntity();
  $id = Html::getUniqueId('lpb-controls');

  $variables['controls'] += [
    'drag_handle' => [
      '#type' => 'link',
      '#title' => t('Drag'),
      '#url' => Url::fromUri('internal:#move'),
      '#attributes' => [
        'class' => [
          'lpb-drag',
          'lpb-tooltip--hover',
          'lpb-tooltip--focus',
        ],
        'aria-describedby' => $id . '--tip',
      ],
      '#weight' => 10,
    ],
    'label' => [
      '#type' => 'html_tag',
      '#tag' => 'span',
      '#attributes' => [
        'class' => ['lpb-controls-label'],
      ],
      '#value' => $entity->getParagraphType()->get('label'),
      '#weight' => 30,
    ],
    'move_up' => [
      '#type' => 'link',
      '#title' => t('Move up'),
      '#url' => Url::fromUri('internal:#move-up'),
      '#attributes' => [
        'class' => ['lpb-up'],
        'title' => t('Move up'),
      ],
      '#weight' => 40,
    ],
    'move_down' => [
      '#type' => 'link',
      '#url' => Url::fromUri('internal:#move-down'),
      '#title' => t('Move down'),
      '#attributes' => [
        'class' => ['lpb-down'],
        'title' => t('Move down'),
      ],
      '#weight' => 50,
    ],
    'edit_link' => [
      '#type' => 'link',
      '#url' => Url::fromRoute('layout_paragraphs.builder.edit_item', [
        'layout_paragraphs_layout' => $layout->id(),
        'component_uuid' => $entity->uuid(),
      ]),
      '#title' => t('Edit'),
      '#attributes' => [
        'class' => [
          'lpb-edit',
          'use-ajax',
        ],
        'data-dialog-type' => 'dialog',
        'data-dialog-options' => Json::encode(Dialog::dialogSettings($layout)),
        'title' => t('Edit'),
      ],
      '#access' => $variables['edit_access'],
      '#weight' => 60,
    ],
    'duplicate_link' => [
      '#type' => 'link',
      '#url' => Url::fromRoute('layout_paragraphs.builder.duplicate_item', [
        'layout_paragraphs_layout' => $layout->id(),
        'source_uuid' => $entity->uuid(),
      ]),
      '#title' => t('Duplicate'),
      '#attributes' => [
        'class' => [
          'lpb-duplicate',
          'use-ajax',
        ],
        'title' => t('Duplicate'),
      ],
      '#access' => $variables['duplicate_access'],
      '#weight' => 65,
    ],
    'delete_link' => [
      '#type' => 'link',
      '#url' => Url::fromRoute('layout_paragraphs.builder.delete_item', [
        'layout_paragraphs_layout' => $layout->id(),
        'component_uuid' => $entity->uuid(),
      ]),
      '#title' => t('Delete'),
      '#attributes' => [
        'class' => [
          'lpb-delete',
          'use-ajax',
        ],
        'data-dialog-type' => 'dialog',
        'data-dialog-options' => Json::encode([
          'modal' => TRUE,
          'target' => Dialog::dialogId($layout),
          'dialogClass' => 'lpb-dialog',
          'width' => 'auto',
        ]),
        'title' => t('Delete'),
      ],
      '#access' => $variables['delete_access'],
      '#weight' => 70,
    ],
  ];
  if ($component->isLayout()) {
    if ($show_layout_plugin_labels) {
      $section = $layout->getLayoutSection($entity);
      $layout_plugin_id = $section->getLayoutId();
      $definition = \Drupal::service('plugin.manager.core.layout')->getDefinition($layout_plugin_id);
      $variables['controls']['label']['#value'] .= ' - ' . $definition->getLabel();
    }
    $variables['attributes']['class'][] = 'is-layout';
  }
}

/**
 * Implements hook_entity_extra_field_info().
 *
 * Adds the layout paragraphs form fields to "Manage form display" tab.
 */
function layout_paragraphs_entity_extra_field_info() {
  $extra = [];
  foreach (ParagraphsType::loadMultiple() as $paragraphs_type) {
    if ($paragraphs_type->hasEnabledBehaviorPlugin('layout_paragraphs')) {
      $extra['paragraph'][$paragraphs_type->id()]['form']['layout_paragraphs_fields'] = [
        'label' => t('Layout selection and configuration form'),
        'visible' => TRUE,
        'weight' => -200,
      ];
    }
  }
  return $extra;
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function layout_paragraphs_form_layout_paragraphs_component_form_alter(&$form, FormStateInterface $form_state) {
  $display = $form['#display'];
  if ($layout_paragraphs_fields = $display->getComponent('layout_paragraphs_fields')) {
    $form['layout_paragraphs']['#weight'] = $layout_paragraphs_fields['weight'];
  }
}

/**
 * Adjusts the field weight based on settings from "Manage form display" tab.
 */
function _layout_paragraphs_form_field_weights(array &$form, FormStateInterface $form_state) {
  $display = $form['#display'];
  if ($layout_paragraphs_fields = $display->getComponent('layout_paragraphs_fields')) {
    $form['layout_paragraphs']['#weight'] = $layout_paragraphs_fields['weight'];
  }
}

/**
 * Implements hook_ENTITY_presave().
 *
 * Updates references to parent layout section uuids in cases
 * where paragraphs are duplicated, for example when using the
 * Replicate module.
 *
 * @todo Consider changing approach if this issue is addressed in core:
 * https://www.drupal.org/project/drupal/issues/3040556
 * (It is not possible to react to an entity being duplicated)
 */
function layout_paragraphs_paragraph_presave(ParagraphInterface $paragraph) {
  $behavior_settings = $paragraph->getAllBehaviorSettings();
  $parent_uuid = $behavior_settings['layout_paragraphs']['parent_uuid'] ?? NULL;
  if (empty($parent_uuid)) {
    return;
  }

  // If there is no host, the parent hasn't been saved and we are not cloning.
  $host = $paragraph->getParentEntity();
  if (empty($host)) {
    return;
  }

  // If the parent component cannot be loaded, we are not cloning.
  /** @var \Drupal\paragraphs\Entity\Paragraph|NULL $parent_component */
  $parent_component = \Drupal::service('entity.repository')
    ->loadEntityByUuid('paragraph', $parent_uuid);
  if (empty($parent_component)) {
    return;
  }

  // If the parent component does not have a parent entity, we are not cloning.
  $parent_host = $parent_component->getParentEntity();
  if (empty($parent_host)) {
    return;
  }

  // If the current paragraph's host's id does not match
  // the paragraph's parent's host's id, we ARE cloning.
  if ($host->id() !== $parent_host->id()) {

    $uuid_map = [];

    // Map the UUIDs to deltas from the clone source.
    $items = _layout_paragraphs_get_paragraphs($parent_component);
    /** @var \Drupal\paragraphs\Entity\Paragraph $item */
    foreach ($items as $delta => $item) {
      $uuid_map[$item->uuid()] = $delta;
    }

    // Map the deltas to UUIds from the clone destination.
    $items = _layout_paragraphs_get_paragraphs($paragraph);
    $delta_map = [];
    /** @var \Drupal\paragraphs\Entity\Paragraph $item */
    foreach ($items as $delta => $item) {
      $delta_map[$delta] = $item->uuid();
    }

    // Assign the correct uuid.
    $parent_delta = $uuid_map[$parent_uuid];
    $correct_uuid = $delta_map[$parent_delta];

    // Since paragraph::preSave() has already been called,
    // we have to set the serialized behavior settings directly
    // rather than using setBehaviorSettings().
    $behavior_settings['layout_paragraphs']['parent_uuid'] = $correct_uuid;
    $paragraph->set('behavior_settings', serialize($behavior_settings));
  }
}

/**
 * Implements hook_entity_translation_create().
 *
 * Supports asynchronous translations by duplicating paragraphs referenced
 * by an entity reference revisions field that is translatable, updating parent
 * uuid references in layouts.
 */
function layout_paragraphs_entity_translation_create(FieldableEntityInterface $entity) {
  $entity_type_manager = \Drupal::entityTypeManager();
  /** @var \Drupal\Core\Entity\RevisionableStorageInterface $paragraph_storage */
  $paragraph_storage = $entity_type_manager->getStorage('paragraph');
  /** @var \Drupal\field\FieldConfigInterface[] $field_definitions */
  $field_definitions = $entity_type_manager->getStorage('field_config')
    ->loadByProperties([
      'entity_type' => $entity->getEntityTypeId(),
      'bundle' => $entity->bundle(),
      'field_type' => 'entity_reference_revisions',
    ]);

  $langcode_key = $entity->getEntityType()->getKey('langcode');
  $langcode = $entity->get($langcode_key)->value;

  foreach ($field_definitions as $field_definition) {
    if ($field_definition->isTranslatable() === FALSE) {
      continue;
    }
    if ($field_definition->getFieldStorageDefinition()->getSetting('target_type') !== 'paragraph') {
      continue;
    }

    $async_values = [];
    $values = $entity->get($field_definition->getName())->getValue() ?? [];

    // Get the list of referenced paragraphs using the available property.
    $paragraphs = array_map(function ($value) use ($paragraph_storage) {
      if (isset($value['entity'])) {
        $paragraph = $value['entity'];
      }
      elseif (isset($value['target_revision_id'])) {
        $paragraph = $paragraph_storage->loadRevision($value['target_revision_id']);
      }
      elseif (isset($value['target_id'])) {
        $paragraph = $paragraph_storage->load($value['target_id']);
      }
      return $paragraph ?? NULL;
    }, $values);

    // Save an array of the original uuids.
    $original_uuids = array_map(function ($paragraph) {
      return $paragraph->uuid();
    }, $paragraphs);

    // Duplicate the paragraphs.
    $duplicates = array_map(function ($paragraph) use ($langcode) {
      $duplicate = $paragraph->createDuplicate();
      $langcode_key = $duplicate->getEntityType()->getKey('langcode');
      $duplicate->set($langcode_key, $langcode);
      return $duplicate;
    }, $paragraphs);

    // Get an array of new uuids so we can map references to parents uuids.
    $new_uuids = array_map(function ($paragraph) {
      return $paragraph->uuid();
    }, $duplicates);

    // Map old uuids to new uuids.
    $uuid_map = array_combine($original_uuids, $new_uuids);

    // Update references to the correct, new parent uuids in the
    // behavior settings.
    $async_values = array_map(function ($paragraph) use ($uuid_map) {
      $behavior_settings = $paragraph->getAllBehaviorSettings();
      $parent_uuid = $behavior_settings['layout_paragraphs']['parent_uuid'] ?? NULL;
      if (!empty($parent_uuid)) {
        $behavior_settings['layout_paragraphs']['parent_uuid'] = $uuid_map[$parent_uuid];
        $paragraph->setAllBehaviorSettings($behavior_settings);
      }
      return $paragraph;
    }, $duplicates);

    $entity->set($field_definition->getName(), $async_values);
  }
}

/**
 * Returns a list of all sibling paragraphs given a single paragraph.
 *
 * The returned list contains all of the referenced entities,
 * including the passed $paragraph.
 *
 * @param \Drupal\paragraphs\ParagraphInterface $paragraph
 *   The paragraph to use for finding the complete list of siblings.
 *
 * @return array
 *   The list of paragraph entities.
 */
function _layout_paragraphs_get_paragraphs(ParagraphInterface $paragraph) {
  $host = $paragraph->getParentEntity();
  $parent_field = $paragraph->get('parent_field_name');
  $field_name = $parent_field->first()->getString();
  /** @var \Drupal\Core\Field\EntityReferenceFieldItemList $item_list */
  $item_list = $host->get($field_name);
  return $item_list->referencedEntities();
}

/**
 * Implements hook_library_info_alter().
 */
function layout_paragraphs_library_info_alter(&$libraries, $extension) {

  if ($extension == 'gin') {
    if (isset($libraries['layout_paragraphs'])) {
      unset($libraries['layout_paragraphs']['css']['component']);
    }
    if (isset($libraries['layout_paragraphs2'])) {
      unset($libraries['layout_paragraphs2']['css']['component']);
    }
  }

  if ($extension !== 'layout_paragraphs') {
    return;
  }

  foreach ($libraries as $name => &$library) {
    if (!isset($library['cdn']) || !isset($library['directory'])) {
      continue;
    }
    if (\Drupal::service('library.libraries_directory_file_finder')->find($name)) {
      continue;
    }
    layout_paragraphs_alter_library_recursive($library, $library['cdn']);
  }
}

/**
 * Alter layout_paragraph libraries.
 *
 * @param array $library
 *   A layout_paragraphs library defined in layout_paragraphs.libraries.yml.
 * @param array $cdn
 *   A associative array of library paths mapped to CDN URL.
 */
function layout_paragraphs_alter_library_recursive(array &$library, array $cdn) {
  foreach ($library as $key => &$value) {
    // CSS and JS files are listed in associative arrays keyed via string.
    if (!is_string($key) || !is_array($value)) {
      continue;
    }

    // Ignore the CDN's associative array.
    if ($key === 'cdn') {
      continue;
    }

    // Replace the CDN sources (i.e. /library/*) with the CDN URL destination
    // (https://cdnjs.cloudflare.com/ajax/libs/*).
    foreach ($cdn as $source => $destination) {
      if (strpos($key, $source) === 0) {
        $uri = str_replace($source, $destination, $key);
        $library[$uri] = $value;
        $library[$uri]['type'] = 'external';
        unset($library[$key]);
        break;
      }
    }

    // Recurse downward to find nested libraries.
    layout_paragraphs_alter_library_recursive($value, $cdn);
  }
}

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

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