FieldLayoutBuilder.php 5.89 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
<?php

namespace Drupal\field_layout;

use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\field_layout\Display\EntityDisplayWithLayoutInterface;
use Drupal\Core\Layout\LayoutPluginManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Builds a field layout.
 */
class FieldLayoutBuilder implements ContainerInjectionInterface {

  /**
   * The layout plugin manager.
   *
   * @var \Drupal\Core\Layout\LayoutPluginManagerInterface
   */
  protected $layoutPluginManager;

  /**
   * The entity field manager.
   *
   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
   */
  protected $entityFieldManager;

  /**
   * Constructs a new FieldLayoutBuilder.
   *
   * @param \Drupal\Core\Layout\LayoutPluginManagerInterface $layout_plugin_manager
   *   The layout plugin manager.
   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
   *   The entity field manager.
   */
  public function __construct(LayoutPluginManagerInterface $layout_plugin_manager, EntityFieldManagerInterface $entity_field_manager) {
    $this->layoutPluginManager = $layout_plugin_manager;
    $this->entityFieldManager = $entity_field_manager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('plugin.manager.core.layout'),
      $container->get('entity_field.manager')
    );
  }

  /**
   * Applies the layout to an entity build.
   *
   * @param array $build
   *   A renderable array representing the entity content or form.
   * @param \Drupal\field_layout\Display\EntityDisplayWithLayoutInterface $display
   *   The entity display holding the display options configured for the entity
   *   components.
   */
  public function buildView(array &$build, EntityDisplayWithLayoutInterface $display) {
    $layout_definition = $this->layoutPluginManager->getDefinition($display->getLayoutId(), FALSE);
    if ($layout_definition && $fields = $this->getFields($build, $display, 'view')) {
      // Add the regions to the $build in the correct order.
      $regions = array_fill_keys($layout_definition->getRegionNames(), []);

      foreach ($fields as $name => $field) {
        // Move the field from the top-level of $build into a region-specific
        // section.
        // @todo Ideally the array structure would remain unchanged, see
        //   https://www.drupal.org/node/2846393.
        $regions[$field['region']][$name] = $build[$name];
        unset($build[$name]);
      }
      // Ensure this will not conflict with any existing array elements by
      // prefixing with an underscore.
      $build['_field_layout'] = $display->getLayout()->build($regions);
    }
  }

  /**
   * Applies the layout to an entity form.
   *
   * @param array $build
   *   A renderable array representing the entity content or form.
   * @param \Drupal\field_layout\Display\EntityDisplayWithLayoutInterface $display
   *   The entity display holding the display options configured for the entity
   *   components.
   */
  public function buildForm(array &$build, EntityDisplayWithLayoutInterface $display) {
    $layout_definition = $this->layoutPluginManager->getDefinition($display->getLayoutId(), FALSE);
    if ($layout_definition && $fields = $this->getFields($build, $display, 'form')) {
      $fill = [];
      $fill['#process'][] = '\Drupal\Core\Render\Element\RenderElement::processGroup';
      $fill['#pre_render'][] = '\Drupal\Core\Render\Element\RenderElement::preRenderGroup';
      // Add the regions to the $build in the correct order.
      $regions = array_fill_keys($layout_definition->getRegionNames(), $fill);

      foreach ($fields as $name => $field) {
        // As this is a form, #group can be used to relocate the fields. This
        // avoids breaking hook_form_alter() implementations by not actually
        // moving the field in the form structure. If a #group is already set,
        // do not overwrite it.
        if (!isset($build[$name]['#group'])) {
          $build[$name]['#group'] = $field['region'];
        }
      }
      // Ensure this will not conflict with any existing array elements by
      // prefixing with an underscore.
      $build['_field_layout'] = $display->getLayout()->build($regions);
    }
  }

  /**
   * Gets the fields that need to be processed.
   *
   * @param array $build
   *   A renderable array representing the entity content or form.
   * @param \Drupal\field_layout\Display\EntityDisplayWithLayoutInterface $display
   *   The entity display holding the display options configured for the entity
   *   components.
   * @param string $display_context
   *   The display context, either 'form' or 'view'.
   *
   * @return array
   *   An array of configurable fields present in the build.
   */
  protected function getFields(array $build, EntityDisplayWithLayoutInterface $display, $display_context) {
    $components = $display->getComponents();

    // Ignore any extra fields from the list of field definitions. Field
    // definitions can have a non-configurable display, but all extra fields are
    // always displayed.
    $field_definitions = array_diff_key(
      $this->entityFieldManager->getFieldDefinitions($display->getTargetEntityTypeId(), $display->getTargetBundle()),
      $this->entityFieldManager->getExtraFields($display->getTargetEntityTypeId(), $display->getTargetBundle())
    );

    $fields_to_exclude = array_filter($field_definitions, function (FieldDefinitionInterface $field_definition) use ($display_context) {
      // Remove fields with a non-configurable display.
      return !$field_definition->isDisplayConfigurable($display_context);
    });
    $components = array_diff_key($components, $fields_to_exclude);

    // Only include fields present in the build.
    $components = array_intersect_key($components, $build);

    return $components;
  }

}