LayoutBuilderController.php 11.2 KB
Newer Older
1 2 3 4
<?php

namespace Drupal\layout_builder\Controller;

5
use Drupal\Core\Ajax\AjaxHelperTrait;
6
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
7
use Drupal\Core\Messenger\MessengerInterface;
8 9 10
use Drupal\Core\Plugin\PluginFormInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
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 17 18 19 20 21 22 23 24 25
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;

/**
 * Defines a controller to provide the Layout Builder admin UI.
 *
 * @internal
 */
class LayoutBuilderController implements ContainerInjectionInterface {

26
  use LayoutBuilderContextTrait;
27
  use StringTranslationTrait;
28
  use AjaxHelperTrait;
29 30 31 32 33 34 35 36

  /**
   * The layout tempstore repository.
   *
   * @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface
   */
  protected $layoutTempstoreRepository;

37 38 39 40 41 42 43
  /**
   * The messenger service.
   *
   * @var \Drupal\Core\Messenger\MessengerInterface
   */
  protected $messenger;

44 45 46 47 48
  /**
   * LayoutBuilderController constructor.
   *
   * @param \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository
   *   The layout tempstore repository.
49 50
   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
   *   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 59 60 61
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
62 63
      $container->get('layout_builder.tempstore_repository'),
      $container->get('messenger')
64 65 66 67 68 69
    );
  }

  /**
   * Provides a title callback.
   *
70 71
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
   *   The section storage.
72 73 74 75
   *
   * @return string
   *   The title for the layout page.
   */
76 77
  public function title(SectionStorageInterface $section_storage) {
    return $this->t('Edit layout for %label', ['%label' => $section_storage->label()]);
78 79 80 81 82
  }

  /**
   * Renders the Layout UI.
   *
83 84
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
   *   The section storage.
85 86 87 88 89 90
   * @param bool $is_rebuilding
   *   (optional) Indicates if the layout is rebuilding, defaults to FALSE.
   *
   * @return array
   *   A render array.
   */
91 92
  public function layout(SectionStorageInterface $section_storage, $is_rebuilding = FALSE) {
    $this->prepareLayout($section_storage, $is_rebuilding);
93 94

    $output = [];
95 96 97 98 99
    if ($this->isAjax()) {
      $output['status_messages'] = [
        '#type' => 'status_messages',
      ];
    }
100
    $count = 0;
101 102 103
    for ($i = 0; $i < $section_storage->count(); $i++) {
      $output[] = $this->buildAddSectionLink($section_storage, $count);
      $output[] = $this->buildAdministrativeSection($section_storage, $count);
104 105
      $count++;
    }
106
    $output[] = $this->buildAddSectionLink($section_storage, $count);
107 108 109 110 111 112 113 114
    $output['#attached']['library'][] = 'layout_builder/drupal.layout_builder';
    $output['#type'] = 'container';
    $output['#attributes']['id'] = 'layout-builder';
    // Mark this UI as uncacheable.
    $output['#cache']['max-age'] = 0;
    return $output;
  }

115 116 117 118 119 120 121 122 123
  /**
   * Prepares a layout for use in the UI.
   *
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
   *   The section storage.
   * @param bool $is_rebuilding
   *   Indicates if the layout is rebuilding.
   */
  protected function prepareLayout(SectionStorageInterface $section_storage, $is_rebuilding) {
124 125 126 127 128
    // If the layout has pending changes, add a warning.
    if ($this->layoutTempstoreRepository->has($section_storage)) {
      $this->messenger->addWarning($this->t('You have unsaved changes.'));
    }

129
    // Only add sections if the layout is new and empty.
130 131
    if (!$is_rebuilding && $section_storage->count() === 0) {
      $sections = [];
132 133 134 135 136 137 138
      // If this is an empty override, copy the sections from the corresponding
      // default.
      if ($section_storage instanceof OverridesSectionStorageInterface) {
        $sections = $section_storage->getDefaultSectionStorage()->getSections();
      }

      // For an empty layout, begin with a single section of one column.
139 140 141 142 143 144 145 146 147 148 149
      if (!$sections) {
        $sections[] = new Section('layout_onecol');
      }

      foreach ($sections as $section) {
        $section_storage->appendSection($section);
      }
      $this->layoutTempstoreRepository->set($section_storage);
    }
  }

150 151 152
  /**
   * Builds a link to add a new section at a given delta.
   *
153 154
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
   *   The section storage.
155 156 157 158 159 160
   * @param int $delta
   *   The delta of the section to splice.
   *
   * @return array
   *   A render array for a link.
   */
161 162 163
  protected function buildAddSectionLink(SectionStorageInterface $section_storage, $delta) {
    $storage_type = $section_storage->getStorageType();
    $storage_id = $section_storage->getStorageId();
164 165 166 167 168 169
    return [
      'link' => [
        '#type' => 'link',
        '#title' => $this->t('Add Section'),
        '#url' => Url::fromRoute('layout_builder.choose_section',
          [
170 171
            'section_storage_type' => $storage_type,
            'section_storage' => $storage_id,
172 173 174 175
            'delta' => $delta,
          ],
          [
            'attributes' => [
176
              'class' => ['use-ajax', 'new-section__link'],
177 178 179 180 181 182 183 184
              'data-dialog-type' => 'dialog',
              'data-dialog-renderer' => 'off_canvas',
            ],
          ]
        ),
      ],
      '#type' => 'container',
      '#attributes' => [
185
        'class' => ['new-section'],
186 187 188 189 190 191 192
      ],
    ];
  }

  /**
   * Builds the render array for the layout section while editing.
   *
193 194
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
   *   The section storage.
195 196 197 198 199 200
   * @param int $delta
   *   The delta of the section.
   *
   * @return array
   *   The render array for a given section.
   */
201 202 203 204
  protected function buildAdministrativeSection(SectionStorageInterface $section_storage, $delta) {
    $storage_type = $section_storage->getStorageType();
    $storage_id = $section_storage->getStorageId();
    $section = $section_storage->getSection($delta);
205

206
    $layout = $section->getLayout();
207
    $build = $section->toRenderArray($this->getAvailableContexts($section_storage), TRUE);
208 209 210 211 212 213 214 215 216 217
    $layout_definition = $layout->getPluginDefinition();

    foreach ($layout_definition->getRegions() as $region => $info) {
      if (!empty($build[$region])) {
        foreach ($build[$region] as $uuid => $block) {
          $build[$region][$uuid]['#attributes']['class'][] = 'draggable';
          $build[$region][$uuid]['#attributes']['data-layout-block-uuid'] = $uuid;
          $build[$region][$uuid]['#contextual_links'] = [
            'layout_builder_block' => [
              'route_parameters' => [
218 219
                'section_storage_type' => $storage_type,
                'section_storage' => $storage_id,
220 221 222 223 224 225 226 227 228 229 230 231 232 233
                'delta' => $delta,
                'region' => $region,
                'uuid' => $uuid,
              ],
            ],
          ];
        }
      }

      $build[$region]['layout_builder_add_block']['link'] = [
        '#type' => 'link',
        '#title' => $this->t('Add Block'),
        '#url' => Url::fromRoute('layout_builder.choose_block',
          [
234 235
            'section_storage_type' => $storage_type,
            'section_storage' => $storage_id,
236 237 238 239 240
            'delta' => $delta,
            'region' => $region,
          ],
          [
            'attributes' => [
241
              'class' => ['use-ajax', 'new-block__link'],
242 243 244 245 246 247 248
              'data-dialog-type' => 'dialog',
              'data-dialog-renderer' => 'off_canvas',
            ],
          ]
        ),
      ];
      $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 252 253 254 255
      $build[$region]['#attributes']['data-region'] = $region;
      $build[$region]['#attributes']['class'][] = 'layout-builder--layout__region';
    }

    $build['#attributes']['data-layout-update-url'] = Url::fromRoute('layout_builder.move_block', [
256 257
      'section_storage_type' => $storage_type,
      'section_storage' => $storage_id,
258 259 260 261 262 263 264 265 266 267 268 269 270 271
    ])->toString();
    $build['#attributes']['data-layout-delta'] = $delta;
    $build['#attributes']['class'][] = 'layout-builder--layout';

    return [
      '#type' => 'container',
      '#attributes' => [
        'class' => ['layout-section'],
      ],
      'configure' => [
        '#type' => 'link',
        '#title' => $this->t('Configure section'),
        '#access' => $layout instanceof PluginFormInterface,
        '#url' => Url::fromRoute('layout_builder.configure_section', [
272 273
          'section_storage_type' => $storage_type,
          'section_storage' => $storage_id,
274 275 276 277 278 279 280 281 282 283 284 285
          'delta' => $delta,
        ]),
        '#attributes' => [
          'class' => ['use-ajax', 'configure-section'],
          'data-dialog-type' => 'dialog',
          'data-dialog-renderer' => 'off_canvas',
        ],
      ],
      'remove' => [
        '#type' => 'link',
        '#title' => $this->t('Remove section'),
        '#url' => Url::fromRoute('layout_builder.remove_section', [
286 287
          'section_storage_type' => $storage_type,
          'section_storage' => $storage_id,
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
          'delta' => $delta,
        ]),
        '#attributes' => [
          'class' => ['use-ajax', 'remove-section'],
          'data-dialog-type' => 'dialog',
          'data-dialog-renderer' => 'off_canvas',
        ],
      ],
      'layout-section' => $build,
    ];
  }

  /**
   * Saves the layout.
   *
303 304
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
   *   The section storage.
305 306 307 308
   *
   * @return \Symfony\Component\HttpFoundation\RedirectResponse
   *   A redirect response.
   */
309 310 311
  public function saveLayout(SectionStorageInterface $section_storage) {
    $section_storage->save();
    $this->layoutTempstoreRepository->delete($section_storage);
312 313 314 315 316 317 318 319

    if ($section_storage instanceof OverridesSectionStorageInterface) {
      $this->messenger->addMessage($this->t('The layout override has been saved.'));
    }
    else {
      $this->messenger->addMessage($this->t('The layout has been saved.'));
    }

320
    return new RedirectResponse($section_storage->getRedirectUrl()->setAbsolute()->toString());
321 322 323 324 325
  }

  /**
   * Cancels the layout.
   *
326 327
   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
   *   The section storage.
328 329 330 331
   *
   * @return \Symfony\Component\HttpFoundation\RedirectResponse
   *   A redirect response.
   */
332 333
  public function cancelLayout(SectionStorageInterface $section_storage) {
    $this->layoutTempstoreRepository->delete($section_storage);
334 335 336

    $this->messenger->addMessage($this->t('The changes to the layout have been discarded.'));

337
    return new RedirectResponse($section_storage->getRedirectUrl()->setAbsolute()->toString());
338 339 340
  }

}