entity_reference_layout.module in Entity Reference with Layout 8
Contains entity_reference_layout.module.
File
entity_reference_layout.moduleView source
<?php
/**
* @file
* Contains entity_reference_layout.module.
*/
use Drupal\Core\Url;
use Drupal\Core\Render\Element;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\entity_reference_layout\Event\ErlMergeAttributesEvent;
use Drupal\Core\Form\FormStateInterface;
/**
* Implements hook_help().
*/
function entity_reference_layout_help($route_name, RouteMatchInterface $route_match) {
$output = '';
switch ($route_name) {
// Main module help for the entity_reference_layout module.
case 'help.page.entity_reference_layout':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('Entity reference field with layouts') . '</p>';
break;
}
return $output;
}
/**
* Implements hook_theme().
*/
function entity_reference_layout_theme() {
return [
'entity_reference_layout_widget' => [
'render element' => 'form',
'function' => 'theme_entity_reference_layout_widget',
],
'entity_reference_layout_radio' => [
'render element' => 'element',
'function' => 'theme_entity_reference_layout_radio',
],
'entity_reference_layout' => [
'variables' => [
'elements' => '',
'content' => '',
],
],
];
}
/**
* Implements hook_theme_suggestions().
*/
function entity_reference_layout_theme_suggestions_entity_reference_layout(array $variables) {
$suggestions = [];
$sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_');
$suggestions[] = 'entity_reference_layout__' . $sanitized_view_mode;
$suggestions[] = 'entity_reference_layout__' . $variables['elements']['field_name'];
$suggestions[] = 'entity_reference_layout__' . $variables['elements']['field_name'] . '__' . $sanitized_view_mode;
return $suggestions;
}
/**
* Implements hook_entity_reference_layout_radio().
*
* Custom theme hook for adding layout icons
* and wrapper HTML to layout select radios.
*/
function theme_entity_reference_layout_radio($element) {
/* @var \Drupal\Core\Layout\LayoutPluginManager $layout_plugin_manager */
$layout_plugin_manager = \Drupal::service('plugin.manager.core.layout');
$renderer = \Drupal::service('renderer');
$layout_name = $element['element']['#return_value'];
try {
/* @var \Drupal\Core\Layout\LayoutDefinition $definition */
$definition = $layout_plugin_manager
->getDefinition($layout_name);
$icon = $definition
->getIcon(40, 60, 1, 0);
$rendered_icon = $renderer
->render($icon);
$layout_item = [
'#type' => 'container',
'#prefix' => '<div class="layout-radio-item">',
'#suffix' => '</div>',
'icon' => [
'#prefix' => '<div class="layout-icon-wrapper">',
'#suffix' => '</div>',
'#markup' => $rendered_icon,
],
'radio' => [
'#type' => 'container',
'#attributes' => [],
'item' => [
'#markup' => $element['element']['#children'],
],
],
];
return \Drupal::service('renderer')
->render($layout_item);
} catch (\Exception $e) {
watchdog_exception('entity_reference_layout', $e);
}
return [];
}
/**
* Merges $layout_options into an $attributes array.
*
* Returned attributes are passed to a rendered layout,
* typically with custom classes to be applied although can
* include other data useful to rendering.
*
* Leverages even dispatcher pattern so other modules
* can add data to attributes.
*/
function entity_reference_layout_merge_attributes(array $attributes, array $layout_options) {
if (!empty($layout_options)) {
if (!empty($layout_options['options']['container_classes'])) {
$attributes['class'][] = $layout_options['options']['container_classes'];
}
if (!empty($layout_options['options']['bg_color'])) {
$attributes['style'] = [
'background-color: ' . $layout_options['options']['bg_color'],
];
}
}
$event = new ErlMergeAttributesEvent($attributes, $layout_options);
$event_dispatcher = \Drupal::service('event_dispatcher');
$event_dispatcher
->dispatch(ErlMergeAttributesEvent::EVENT_NAME, $event);
return $attributes;
}
/**
* Helper function to sort array items by '_weight'.
*/
function _erl_widget_sort_helper($a, $b) {
$a_weight = is_array($a) && isset($a['_weight']['#value']) ? $a['_weight']['#value'] : 0;
$b_weight = is_array($b) && isset($b['_weight']['#value']) ? $b['_weight']['#value'] : 0;
return $a_weight - $b_weight;
}
/**
* Themes the "ERL" field widget.
*
* @param array $variables
* Contains the form element data from $element['entities'].
*/
function theme_entity_reference_layout_widget(array $variables) {
$currentUser = \Drupal::service('current_user');
$form = $variables['form'];
$build = [
'#type' => 'fieldset',
'#id' => $form['#id'],
'#attributes' => [
'class' => [
'erl-field',
],
],
'#title' => $form['#title'],
// These get moved around with JS.
'add_more' => [
'#weight' => 998,
] + $form['add_more'],
// Container for layouts.
'layout_items' => [
'#type' => 'container',
'#attributes' => [
'class' => [
'erl-layout-wrapper',
],
],
],
// Container for disabled / orphaned items.
'disabled' => [
'#type' => 'fieldset',
'#attributes' => [
'class' => [
'erl-disabled-items',
],
],
'#weight' => 999,
'#title' => t('Disabled Items'),
'items' => [
'#type' => 'container',
'#attributes' => [
'class' => [
'erl-disabled-wrapper',
],
],
'description' => [
'#type' => 'html_tag',
'#tag' => 'div',
'#attributes' => [
'class' => [
'erl-disabled-items__description',
],
],
'#value' => t('Drop items here that you want to keep disabled / hidden, without removing them permanently.'),
],
],
],
];
// Add a Warning Message if this is a Translation Context.
if (!empty($build["add_more"]["actions"]["#isTranslating"])) {
$build['is_translating_warning'] = [
'#type' => "html_tag",
'#tag' => 'div',
'#value' => t("This is Translation Context (editing a version not in the original language). <b>No new Layout Sections and Items can be added</b>."),
'#weight' => -1100,
'#attributes' => [
'class' => [
'is_translating_warning',
],
],
];
}
$new_items = [];
$form_with_layout = [];
$renderer = \Drupal::service('renderer');
/* @var \Drupal\Core\Layout\LayoutPluginManager $layout_plugin_manager */
$layout_plugin_manager = \Drupal::service('plugin.manager.core.layout');
$items = [];
foreach (Element::children($form) as $key) {
if ($key !== 'add_more') {
// If there is an open entity form,
// move it to it's own section of the widget form.
if (!empty($form[$key]['entity_form'])) {
$build['entity_form'] = $form[$key]['entity_form'] + [
'#weight' => 1000,
];
$build['entity_form']['#attributes']['class'][] = 'erl-entity-form';
unset($form[$key]['entity_form']);
}
// If there is an open remove confirmation form,
// move it to it's own section of the widget form.
if (!empty($form[$key]['remove_form'])) {
$build['remove_form'] = $form[$key]['remove_form'] + [
'#weight' => 1000,
];
unset($form[$key]['remove_form']);
}
$items[] =& $form[$key];
}
}
usort($items, '_erl_widget_sort_helper');
$has_non_layout_items = FALSE;
$section_key = NULL;
foreach ($items as $key => &$item) {
// Merge and add attributes.
$attributes = entity_reference_layout_merge_attributes($item['#attributes'], $item['#layout_options']);
$item['#attributes'] = $attributes;
// Set the weight for sorted list items.
$item['#weight'] = $key;
// If this is a layout we'll populate regions below.
$item['#regions'] = [
'#attributes' => [
'class' => [],
],
];
// Add class for weight element.
if (!empty($item['_weight'])) {
$item['_weight']['#attributes']['class'] = [
'erl-weight',
];
// Move to top of container to ensure items are given the correct delta.
$item['_weight']['#weight'] = -1000;
$item['_weight']['#theme_wrappers'] = [
'container' => [
'#attributes' => [
'class' => [
'hidden',
],
],
],
];
}
// Stash new items for processing later.
if (!empty($item['#is_new'])) {
$new_items[] = $item;
}
elseif (!empty($item['layout']['#value'])) {
$section_key = $key;
$form_with_layout['section_' . $section_key] = $items[$section_key];
$form_with_layout['section_' . $section_key]['#attributes']['class'][] = 'erl-layout';
try {
$layout_instance = $layout_plugin_manager
->createInstance($items[$section_key]['layout']['#value'], $items[$section_key]['config']['#value']);
foreach ($layout_instance
->getPluginDefinition()
->getRegionNames() as $region_name) {
$form_with_layout['section_' . $section_key]['#regions'][$region_name] = [
'#attributes' => [
'class' => [
'erl-layout-region',
'erl-layout-region--' . $region_name,
],
],
];
}
} catch (\Exception $e) {
watchdog_exception('Erl, Layout Plugin Manager', $e);
}
}
elseif (!empty($item['region']['#value']) && isset($form_with_layout['section_' . $section_key]['#regions'][$item['region']['#value']])) {
$form_with_layout['section_' . $section_key]['#regions'][$item['region']['#value']][] = $item;
$has_non_layout_items = TRUE;
}
else {
$build['disabled']['items'][] = $item;
$has_non_layout_items = TRUE;
}
}
// Move new items into correct position, if applicable.
foreach ($new_items as $key => $item) {
$section_key = $item['#parent_weight'];
// Layout items.
if (empty($item['#new_region'])) {
$item['#weight'] = $item['#parent_weight'] + 0.5;
$form_with_layout['new_section_' . $key] = $item;
}
// Items to add into regions.
if (isset($item['#new_region'])) {
$region_name = $item['#new_region'];
if (isset($form_with_layout['section_' . $section_key]['#regions'][$region_name])) {
$form_with_layout['section_' . $section_key]['#regions'][$region_name][] = $item;
}
}
}
$show_layout_labels = \Drupal::config('entity_reference_layout.settings')
->get('show_layout_labels');
foreach ($form_with_layout as $key => $section) {
if (!empty($section['layout']['#value'])) {
try {
$layout_instance = $layout_plugin_manager
->createInstance($section['layout']['#value'], $section['config']['#value']);
// Add a "Add Item" button, if is not a translation context.
if (empty($build["add_more"]["actions"]["#isTranslating"])) {
foreach (array_keys($section['#regions']) as $region) {
$section['#regions'][$region]['button'] = [
'#markup' => '<button class="erl-add-content__toggle">+<span class="visually-hidden">' . t('Add Item') . '</span></button>',
'#allowed_tags' => [
'button',
'span',
],
// Toggle is always last in region.
'#weight' => 10000,
];
}
}
$rendered_regions = $layout_instance
->build($section['#regions']);
$section['#regions']['#attributes']['class'][] = 'erl-layout__regions';
if ($show_layout_labels === 1) {
$label = $layout_instance
->getPluginDefinition() ? $layout_instance
->getPluginDefinition()
->getLabel()
->__toString() : $section['#layout'];
$section['layout']['label'] = [
'#type' => 'label',
'#title' => $label,
'#title_display' => $label,
'#attributes' => [
'class' => [
'paragraph-layout-label',
],
],
];
}
$section['preview']['content'] = [
'#weight' => 1000,
'regions' => $rendered_regions,
];
} catch (\Exception $e) {
watchdog_exception('Erl, Layout Plugin Manager', $e);
}
// Add a "Add Section" button, if is not a translation context.
if ($currentUser
->hasPermission('manage entity reference layout sections') && empty($build["add_more"]["actions"]["#isTranslating"])) {
// Add the "add section" button for js.
$section['button'] = [
'#markup' => '<div class="erl-add-content--single"><button class="erl-add-section"><span class="icon">+</span>' . t('Add Section') . '</button></div>',
'#allowed_tags' => [
'button',
'span',
'div',
],
];
}
}
$build['layout_items'][] = $section;
}
if (count($form_with_layout) == 0) {
$build['add_section_button'] = [
'#markup' => '<div class="erl-first-section"><div class="erl-add-content--single"><button class="erl-add-section"><span class="icon">+</span>' . t('Add Section') . '</button></div></div>',
'#allowed_tags' => [
'button',
'span',
'div',
],
'#weight' => -1,
];
}
$show_labels = \Drupal::config('entity_reference_layout.settings')
->get('show_paragraph_labels');
if ($show_labels) {
foreach ($build["layout_items"] as $key => $layout_item) {
if (!isset($layout_item["preview"]["content"]["regions"])) {
continue;
}
foreach ($build['layout_items'][$key]['preview']['content']['regions'] as $regionKey => $region) {
if (!is_array($region)) {
continue;
}
foreach ($region as $paragraphKey => $paragraph) {
if (!isset($paragraph['#entity'])) {
continue;
}
$entity = $paragraph['#entity'];
$label = $entity
->getParagraphType()->label;
$build['layout_items'][$key]['preview']['content']['regions'][$regionKey][$paragraphKey][] = [
'#type' => 'label',
'#title' => $label,
'#title_display' => $label,
'#attributes' => [
'class' => [
'paragraph-type-label',
],
],
];
}
}
}
}
// If there are no items, don't show the disabled region.
if (!$has_non_layout_items) {
unset($build['disabled']);
}
// Add a container so Javascript can respond to empty state.
if (count($items) == 0) {
$build['empty_container'] = [
'#type' => 'container',
'#attributes' => [
'class' => [
'erl-empty',
],
],
];
}
$build['#attached']['library'][] = 'entity_reference_layout/erl_widget';
return $renderer
->render($build);
}
/**
* Implements hook_form_FORM_ID_alter() for 'field_ui_field_storage_add_form'.
*/
function entity_reference_layout_form_field_ui_field_storage_add_form_alter(array &$form) {
if (isset($form['add']['new_storage_type']['#options'][(string) t('Reference revisions')]['field_ui:entity_reference_layout_revisioned:paragraph'])) {
// @todo Figure out why this option breaks the field config form
// and reintroduce it if possible.
// See https://www.drupal.org/project/entity_reference_layout/issues/3041126
unset($form['add']['new_storage_type']['#options'][(string) t('Reference revisions')]['field_ui:entity_reference_layout_revisioned:paragraph']);
}
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Indicate unsupported multilingual ERL field configuration.
*
* @see paragraphs_form_field_config_edit_form_alter
*/
function entity_reference_layout_form_field_config_edit_form_alter(&$form, FormStateInterface $form_state, $form_id) {
/* @var \Drupal\field\Entity\FieldConfig $field */
$field = $form_state
->getFormObject()
->getEntity();
if (!\Drupal::hasService('content_translation.manager')) {
return;
}
$bundle_is_translatable = \Drupal::service('content_translation.manager')
->isEnabled($field
->getTargetEntityTypeId(), $field
->getTargetBundle());
if (!$bundle_is_translatable || $field
->getType() != 'entity_reference_layout_revisioned' || $field
->getSetting('target_type') != 'paragraph') {
return;
}
// This is a translatable ERL field pointing to a paragraph.
$message_display = 'warning';
$message_text = t('Paragraphs fields do not support translation. See the <a href=":documentation">online documentation</a>.', [
':documentation' => Url::fromUri('https://www.drupal.org/node/2735121')
->toString(),
]);
if ($form['translatable']['#default_value'] == TRUE) {
$message_display = 'error';
}
$form['paragraphs_message'] = [
'#type' => 'container',
'#markup' => $message_text,
'#attributes' => [
'class' => [
'messages messages--' . $message_display,
],
],
'#weight' => 0,
];
}
/**
* Implements hook_module_implements_alter().
*
* If "content_translation", move the form_alter implementation by the
* entity_reference_layout 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
* entity_reference_layout_form_field_config_edit_form_alter.
*
* @see: https://www.hashbangcode.com/article/drupal-8-altering-hook-weights.
*/
function entity_reference_layout_module_implements_alter(&$implementations, $hook) {
// Move our hook_entity_type_alter() implementation to the end of the list.
if ($hook == 'form_alter' && isset($implementations['entity_reference_layout']) && isset($implementations['content_translation'])) {
$hook_init = $implementations['entity_reference_layout'];
unset($implementations['entity_reference_layout']);
$implementations['entity_reference_layout'] = $hook_init;
}
}
Functions
Name | Description |
---|---|
entity_reference_layout_form_field_config_edit_form_alter | Implements hook_form_FORM_ID_alter(). |
entity_reference_layout_form_field_ui_field_storage_add_form_alter | Implements hook_form_FORM_ID_alter() for 'field_ui_field_storage_add_form'. |
entity_reference_layout_help | Implements hook_help(). |
entity_reference_layout_merge_attributes | Merges $layout_options into an $attributes array. |
entity_reference_layout_module_implements_alter | Implements hook_module_implements_alter(). |
entity_reference_layout_theme | Implements hook_theme(). |
entity_reference_layout_theme_suggestions_entity_reference_layout | Implements hook_theme_suggestions(). |
theme_entity_reference_layout_radio | Implements hook_entity_reference_layout_radio(). |
theme_entity_reference_layout_widget | Themes the "ERL" field widget. |
_erl_widget_sort_helper | Helper function to sort array items by '_weight'. |