Pull merge.
[yaffs-website] / web / core / modules / layout_builder / src / Controller / LayoutBuilderController.php
1 <?php
2
3 namespace Drupal\layout_builder\Controller;
4
5 use Drupal\Core\Ajax\AjaxHelperTrait;
6 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
7 use Drupal\Core\Messenger\MessengerInterface;
8 use Drupal\Core\Plugin\PluginFormInterface;
9 use Drupal\Core\StringTranslation\StringTranslationTrait;
10 use Drupal\Core\Url;
11 use Drupal\layout_builder\Context\LayoutBuilderContextTrait;
12 use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
13 use Drupal\layout_builder\OverridesSectionStorageInterface;
14 use Drupal\layout_builder\Section;
15 use Drupal\layout_builder\SectionStorageInterface;
16 use Symfony\Component\DependencyInjection\ContainerInterface;
17 use Symfony\Component\HttpFoundation\RedirectResponse;
18
19 /**
20  * Defines a controller to provide the Layout Builder admin UI.
21  *
22  * @internal
23  */
24 class LayoutBuilderController implements ContainerInjectionInterface {
25
26   use LayoutBuilderContextTrait;
27   use StringTranslationTrait;
28   use AjaxHelperTrait;
29
30   /**
31    * The layout tempstore repository.
32    *
33    * @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface
34    */
35   protected $layoutTempstoreRepository;
36
37   /**
38    * The messenger service.
39    *
40    * @var \Drupal\Core\Messenger\MessengerInterface
41    */
42   protected $messenger;
43
44   /**
45    * LayoutBuilderController constructor.
46    *
47    * @param \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository
48    *   The layout tempstore repository.
49    * @param \Drupal\Core\Messenger\MessengerInterface $messenger
50    *   The messenger service.
51    */
52   public function __construct(LayoutTempstoreRepositoryInterface $layout_tempstore_repository, MessengerInterface $messenger) {
53     $this->layoutTempstoreRepository = $layout_tempstore_repository;
54     $this->messenger = $messenger;
55   }
56
57   /**
58    * {@inheritdoc}
59    */
60   public static function create(ContainerInterface $container) {
61     return new static(
62       $container->get('layout_builder.tempstore_repository'),
63       $container->get('messenger')
64     );
65   }
66
67   /**
68    * Provides a title callback.
69    *
70    * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
71    *   The section storage.
72    *
73    * @return string
74    *   The title for the layout page.
75    */
76   public function title(SectionStorageInterface $section_storage) {
77     return $this->t('Edit layout for %label', ['%label' => $section_storage->label()]);
78   }
79
80   /**
81    * Renders the Layout UI.
82    *
83    * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
84    *   The section storage.
85    * @param bool $is_rebuilding
86    *   (optional) Indicates if the layout is rebuilding, defaults to FALSE.
87    *
88    * @return array
89    *   A render array.
90    */
91   public function layout(SectionStorageInterface $section_storage, $is_rebuilding = FALSE) {
92     $this->prepareLayout($section_storage, $is_rebuilding);
93
94     $output = [];
95     if ($this->isAjax()) {
96       $output['status_messages'] = [
97         '#type' => 'status_messages',
98       ];
99     }
100     $count = 0;
101     for ($i = 0; $i < $section_storage->count(); $i++) {
102       $output[] = $this->buildAddSectionLink($section_storage, $count);
103       $output[] = $this->buildAdministrativeSection($section_storage, $count);
104       $count++;
105     }
106     $output[] = $this->buildAddSectionLink($section_storage, $count);
107     $output['#attached']['library'][] = 'layout_builder/drupal.layout_builder';
108     $output['#type'] = 'container';
109     $output['#attributes']['id'] = 'layout-builder';
110     // Mark this UI as uncacheable.
111     $output['#cache']['max-age'] = 0;
112     return $output;
113   }
114
115   /**
116    * Prepares a layout for use in the UI.
117    *
118    * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
119    *   The section storage.
120    * @param bool $is_rebuilding
121    *   Indicates if the layout is rebuilding.
122    */
123   protected function prepareLayout(SectionStorageInterface $section_storage, $is_rebuilding) {
124     // If the layout has pending changes, add a warning.
125     if ($this->layoutTempstoreRepository->has($section_storage)) {
126       $this->messenger->addWarning($this->t('You have unsaved changes.'));
127     }
128
129     // Only add sections if the layout is new and empty.
130     if (!$is_rebuilding && $section_storage->count() === 0) {
131       $sections = [];
132       // If this is an empty override, copy the sections from the corresponding
133       // default.
134       if ($section_storage instanceof OverridesSectionStorageInterface) {
135         $sections = $section_storage->getDefaultSectionStorage()->getSections();
136       }
137
138       // For an empty layout, begin with a single section of one column.
139       if (!$sections) {
140         $sections[] = new Section('layout_onecol');
141       }
142
143       foreach ($sections as $section) {
144         $section_storage->appendSection($section);
145       }
146       $this->layoutTempstoreRepository->set($section_storage);
147     }
148   }
149
150   /**
151    * Builds a link to add a new section at a given delta.
152    *
153    * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
154    *   The section storage.
155    * @param int $delta
156    *   The delta of the section to splice.
157    *
158    * @return array
159    *   A render array for a link.
160    */
161   protected function buildAddSectionLink(SectionStorageInterface $section_storage, $delta) {
162     $storage_type = $section_storage->getStorageType();
163     $storage_id = $section_storage->getStorageId();
164     return [
165       'link' => [
166         '#type' => 'link',
167         '#title' => $this->t('Add Section'),
168         '#url' => Url::fromRoute('layout_builder.choose_section',
169           [
170             'section_storage_type' => $storage_type,
171             'section_storage' => $storage_id,
172             'delta' => $delta,
173           ],
174           [
175             'attributes' => [
176               'class' => ['use-ajax', 'new-section__link'],
177               'data-dialog-type' => 'dialog',
178               'data-dialog-renderer' => 'off_canvas',
179             ],
180           ]
181         ),
182       ],
183       '#type' => 'container',
184       '#attributes' => [
185         'class' => ['new-section'],
186       ],
187     ];
188   }
189
190   /**
191    * Builds the render array for the layout section while editing.
192    *
193    * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
194    *   The section storage.
195    * @param int $delta
196    *   The delta of the section.
197    *
198    * @return array
199    *   The render array for a given section.
200    */
201   protected function buildAdministrativeSection(SectionStorageInterface $section_storage, $delta) {
202     $storage_type = $section_storage->getStorageType();
203     $storage_id = $section_storage->getStorageId();
204     $section = $section_storage->getSection($delta);
205
206     $layout = $section->getLayout();
207     $build = $section->toRenderArray($this->getAvailableContexts($section_storage), TRUE);
208     $layout_definition = $layout->getPluginDefinition();
209
210     foreach ($layout_definition->getRegions() as $region => $info) {
211       if (!empty($build[$region])) {
212         foreach ($build[$region] as $uuid => $block) {
213           $build[$region][$uuid]['#attributes']['class'][] = 'draggable';
214           $build[$region][$uuid]['#attributes']['data-layout-block-uuid'] = $uuid;
215           $build[$region][$uuid]['#contextual_links'] = [
216             'layout_builder_block' => [
217               'route_parameters' => [
218                 'section_storage_type' => $storage_type,
219                 'section_storage' => $storage_id,
220                 'delta' => $delta,
221                 'region' => $region,
222                 'uuid' => $uuid,
223               ],
224             ],
225           ];
226         }
227       }
228
229       $build[$region]['layout_builder_add_block']['link'] = [
230         '#type' => 'link',
231         '#title' => $this->t('Add Block'),
232         '#url' => Url::fromRoute('layout_builder.choose_block',
233           [
234             'section_storage_type' => $storage_type,
235             'section_storage' => $storage_id,
236             'delta' => $delta,
237             'region' => $region,
238           ],
239           [
240             'attributes' => [
241               'class' => ['use-ajax', 'new-block__link'],
242               'data-dialog-type' => 'dialog',
243               'data-dialog-renderer' => 'off_canvas',
244             ],
245           ]
246         ),
247       ];
248       $build[$region]['layout_builder_add_block']['#type'] = 'container';
249       $build[$region]['layout_builder_add_block']['#attributes'] = ['class' => ['new-block']];
250       $build[$region]['layout_builder_add_block']['#weight'] = 1000;
251       $build[$region]['#attributes']['data-region'] = $region;
252       $build[$region]['#attributes']['class'][] = 'layout-builder--layout__region';
253     }
254
255     $build['#attributes']['data-layout-update-url'] = Url::fromRoute('layout_builder.move_block', [
256       'section_storage_type' => $storage_type,
257       'section_storage' => $storage_id,
258     ])->toString();
259     $build['#attributes']['data-layout-delta'] = $delta;
260     $build['#attributes']['class'][] = 'layout-builder--layout';
261
262     return [
263       '#type' => 'container',
264       '#attributes' => [
265         'class' => ['layout-section'],
266       ],
267       'configure' => [
268         '#type' => 'link',
269         '#title' => $this->t('Configure section'),
270         '#access' => $layout instanceof PluginFormInterface,
271         '#url' => Url::fromRoute('layout_builder.configure_section', [
272           'section_storage_type' => $storage_type,
273           'section_storage' => $storage_id,
274           'delta' => $delta,
275         ]),
276         '#attributes' => [
277           'class' => ['use-ajax', 'configure-section'],
278           'data-dialog-type' => 'dialog',
279           'data-dialog-renderer' => 'off_canvas',
280         ],
281       ],
282       'remove' => [
283         '#type' => 'link',
284         '#title' => $this->t('Remove section <span class="visually-hidden">@section</span>', ['@section' => $delta + 1]),
285         '#url' => Url::fromRoute('layout_builder.remove_section', [
286           'section_storage_type' => $storage_type,
287           'section_storage' => $storage_id,
288           'delta' => $delta,
289         ]),
290         '#attributes' => [
291           'class' => ['use-ajax', 'remove-section'],
292           'data-dialog-type' => 'dialog',
293           'data-dialog-renderer' => 'off_canvas',
294         ],
295       ],
296       'layout-section' => $build,
297     ];
298   }
299
300   /**
301    * Saves the layout.
302    *
303    * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
304    *   The section storage.
305    *
306    * @return \Symfony\Component\HttpFoundation\RedirectResponse
307    *   A redirect response.
308    */
309   public function saveLayout(SectionStorageInterface $section_storage) {
310     $section_storage->save();
311     $this->layoutTempstoreRepository->delete($section_storage);
312
313     if ($section_storage instanceof OverridesSectionStorageInterface) {
314       $this->messenger->addMessage($this->t('The layout override has been saved.'));
315     }
316     else {
317       $this->messenger->addMessage($this->t('The layout has been saved.'));
318     }
319
320     return new RedirectResponse($section_storage->getRedirectUrl()->setAbsolute()->toString());
321   }
322
323   /**
324    * Cancels the layout.
325    *
326    * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
327    *   The section storage.
328    *
329    * @return \Symfony\Component\HttpFoundation\RedirectResponse
330    *   A redirect response.
331    */
332   public function cancelLayout(SectionStorageInterface $section_storage) {
333     $this->layoutTempstoreRepository->delete($section_storage);
334
335     $this->messenger->addMessage($this->t('The changes to the layout have been discarded.'));
336
337     return new RedirectResponse($section_storage->getRedirectUrl()->setAbsolute()->toString());
338   }
339
340 }