annotate core/modules/layout_builder/src/Controller/LayoutBuilderController.php @ 17:129ea1e6d783

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