3 namespace Drupal\layout_builder\Plugin\SectionStorage;
5 use Drupal\Core\Access\AccessResult;
6 use Drupal\Core\Entity\EntityFieldManagerInterface;
7 use Drupal\Core\Entity\EntityTypeInterface;
8 use Drupal\Core\Entity\EntityTypeManagerInterface;
9 use Drupal\Core\Entity\FieldableEntityInterface;
10 use Drupal\Core\Field\FieldItemListInterface;
11 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
12 use Drupal\Core\Plugin\Context\EntityContext;
13 use Drupal\Core\Session\AccountInterface;
15 use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay;
16 use Drupal\layout_builder\OverridesSectionStorageInterface;
17 use Drupal\layout_builder\SectionListInterface;
18 use Symfony\Component\DependencyInjection\ContainerInterface;
19 use Symfony\Component\Routing\RouteCollection;
22 * Defines the 'overrides' section storage type.
29 * Layout Builder is currently experimental and should only be leveraged by
30 * experimental modules and development releases of contributed modules.
31 * See https://www.drupal.org/core/experimental for more information.
33 class OverridesSectionStorage extends SectionStorageBase implements ContainerFactoryPluginInterface, OverridesSectionStorageInterface, SectionStorageLocalTaskProviderInterface {
36 * The field name used by this storage.
40 const FIELD_NAME = 'layout_builder__layout';
43 * The entity type manager.
45 * @var \Drupal\Core\Entity\EntityTypeManagerInterface
47 protected $entityTypeManager;
50 * The entity field manager.
52 * @var \Drupal\Core\Entity\EntityFieldManagerInterface
54 protected $entityFieldManager;
59 * @var \Drupal\layout_builder\SectionListInterface|\Drupal\Core\Field\FieldItemListInterface
61 protected $sectionList;
66 public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager) {
67 parent::__construct($configuration, $plugin_id, $plugin_definition);
69 $this->entityTypeManager = $entity_type_manager;
70 $this->entityFieldManager = $entity_field_manager;
76 public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
81 $container->get('entity_type.manager'),
82 $container->get('entity_field.manager')
89 public function setSectionList(SectionListInterface $section_list) {
90 if (!$section_list instanceof FieldItemListInterface) {
91 throw new \InvalidArgumentException('Overrides expect a field-based section list');
94 return parent::setSectionList($section_list);
98 * Gets the entity storing the overrides.
100 * @return \Drupal\Core\Entity\FieldableEntityInterface
101 * The entity storing the overrides.
103 protected function getEntity() {
104 return $this->getSectionList()->getEntity();
110 public function getStorageId() {
111 $entity = $this->getEntity();
112 return $entity->getEntityTypeId() . '.' . $entity->id();
118 public function extractIdFromRoute($value, $definition, $name, array $defaults) {
119 if (strpos($value, '.') !== FALSE) {
123 if (isset($defaults['entity_type_id']) && !empty($defaults[$defaults['entity_type_id']])) {
124 $entity_type_id = $defaults['entity_type_id'];
125 $entity_id = $defaults[$entity_type_id];
126 return $entity_type_id . '.' . $entity_id;
133 public function getSectionListFromId($id) {
134 if (strpos($id, '.') !== FALSE) {
135 list($entity_type_id, $entity_id) = explode('.', $id, 2);
136 $entity = $this->entityTypeManager->getStorage($entity_type_id)->load($entity_id);
137 if ($entity instanceof FieldableEntityInterface && $entity->hasField(static::FIELD_NAME)) {
138 return $entity->get(static::FIELD_NAME);
141 throw new \InvalidArgumentException(sprintf('The "%s" ID for the "%s" section storage type is invalid', $id, $this->getStorageType()));
147 public function buildRoutes(RouteCollection $collection) {
148 foreach ($this->getEntityTypes() as $entity_type_id => $entity_type) {
150 $defaults['entity_type_id'] = $entity_type_id;
153 if ($this->hasIntegerId($entity_type)) {
154 $requirements[$entity_type_id] = '\d+';
158 // Ensure that upcasting is run in the correct order.
159 $options['parameters']['section_storage'] = [];
160 $options['parameters'][$entity_type_id]['type'] = 'entity:' . $entity_type_id;
162 $template = $entity_type->getLinkTemplate('canonical') . '/layout';
163 $this->buildLayoutRoutes($collection, $this->getPluginDefinition(), $template, $defaults, $requirements, $options, $entity_type_id);
170 public function buildLocalTasks($base_plugin_definition) {
172 foreach ($this->getEntityTypes() as $entity_type_id => $entity_type) {
173 $local_tasks["layout_builder.overrides.$entity_type_id.view"] = $base_plugin_definition + [
174 'route_name' => "layout_builder.overrides.$entity_type_id.view",
176 'title' => $this->t('Layout'),
177 'base_route' => "entity.$entity_type_id.canonical",
178 'cache_contexts' => ['layout_builder_is_active:' . $entity_type_id],
180 $local_tasks["layout_builder.overrides.$entity_type_id.save"] = $base_plugin_definition + [
181 'route_name' => "layout_builder.overrides.$entity_type_id.save",
182 'title' => $this->t('Save Layout'),
183 'parent_id' => "layout_builder_ui:layout_builder.overrides.$entity_type_id.view",
184 'cache_contexts' => ['layout_builder_is_active:' . $entity_type_id],
186 $local_tasks["layout_builder.overrides.$entity_type_id.cancel"] = $base_plugin_definition + [
187 'route_name' => "layout_builder.overrides.$entity_type_id.cancel",
188 'title' => $this->t('Cancel Layout'),
189 'parent_id' => "layout_builder_ui:layout_builder.overrides.$entity_type_id.view",
191 'cache_contexts' => ['layout_builder_is_active:' . $entity_type_id],
193 // @todo This link should be conditionally displayed, see
194 // https://www.drupal.org/node/2917777.
195 $local_tasks["layout_builder.overrides.$entity_type_id.revert"] = $base_plugin_definition + [
196 'route_name' => "layout_builder.overrides.$entity_type_id.revert",
197 'title' => $this->t('Revert to defaults'),
198 'parent_id' => "layout_builder_ui:layout_builder.overrides.$entity_type_id.view",
200 'cache_contexts' => ['layout_builder_is_active:' . $entity_type_id],
207 * Determines if this entity type's ID is stored as an integer.
209 * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
213 * TRUE if this entity type's ID key is always an integer, FALSE otherwise.
215 protected function hasIntegerId(EntityTypeInterface $entity_type) {
216 $field_storage_definitions = $this->entityFieldManager->getFieldStorageDefinitions($entity_type->id());
217 return $field_storage_definitions[$entity_type->getKey('id')]->getType() === 'integer';
221 * Returns an array of relevant entity types.
223 * @return \Drupal\Core\Entity\EntityTypeInterface[]
224 * An array of entity types.
226 protected function getEntityTypes() {
227 return array_filter($this->entityTypeManager->getDefinitions(), function (EntityTypeInterface $entity_type) {
228 return $entity_type->entityClassImplements(FieldableEntityInterface::class) && $entity_type->hasViewBuilderClass() && $entity_type->hasLinkTemplate('canonical');
235 public function getDefaultSectionStorage() {
236 // @todo Expand to work for all view modes in
237 // https://www.drupal.org/node/2907413.
238 return LayoutBuilderEntityViewDisplay::collectRenderDisplay($this->getEntity(), 'full');
244 public function getRedirectUrl() {
245 return $this->getEntity()->toUrl('canonical');
251 public function getLayoutBuilderUrl($rel = 'view') {
252 $entity = $this->getEntity();
253 $route_parameters[$entity->getEntityTypeId()] = $entity->id();
254 return Url::fromRoute("layout_builder.{$this->getStorageType()}.{$this->getEntity()->getEntityTypeId()}.$rel", $route_parameters);
260 public function getContexts() {
261 $entity = $this->getEntity();
262 $contexts['layout_builder.entity'] = EntityContext::fromEntity($entity);
269 public function label() {
270 return $this->getEntity()->label();
276 public function save() {
277 return $this->getEntity()->save();
283 public function access($operation, AccountInterface $account = NULL, $return_as_object = FALSE) {
284 $default_section_storage = $this->getDefaultSectionStorage();
285 $result = AccessResult::allowedIf($default_section_storage->isLayoutBuilderEnabled())->addCacheableDependency($default_section_storage);
286 return $return_as_object ? $result : $result->isAllowed();