PageRenderTime 39ms CodeModel.GetById 9ms RepoModel.GetById 1ms app.codeStats 0ms

/web/core/modules/layout_builder/src/Element/LayoutBuilder.php

https://gitlab.com/mohamed_hussein/prodt
PHP | 394 lines | 271 code | 28 blank | 95 comment | 12 complexity | 3b17d8294f119062a84ffadab9d2441a MD5 | raw file
  1. <?php
  2. namespace Drupal\layout_builder\Element;
  3. use Drupal\Core\Ajax\AjaxHelperTrait;
  4. use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
  5. use Drupal\Core\Plugin\PluginFormInterface;
  6. use Drupal\Core\Render\Element;
  7. use Drupal\Core\Render\Element\RenderElement;
  8. use Drupal\Core\Url;
  9. use Drupal\layout_builder\Context\LayoutBuilderContextTrait;
  10. use Drupal\layout_builder\Event\PrepareLayoutEvent;
  11. use Drupal\layout_builder\LayoutBuilderEvents;
  12. use Drupal\layout_builder\LayoutBuilderHighlightTrait;
  13. use Drupal\layout_builder\SectionStorageInterface;
  14. use Symfony\Component\DependencyInjection\ContainerInterface;
  15. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  16. /**
  17. * Defines a render element for building the Layout Builder UI.
  18. *
  19. * @RenderElement("layout_builder")
  20. *
  21. * @internal
  22. * Plugin classes are internal.
  23. */
  24. class LayoutBuilder extends RenderElement implements ContainerFactoryPluginInterface {
  25. use AjaxHelperTrait;
  26. use LayoutBuilderContextTrait;
  27. use LayoutBuilderHighlightTrait;
  28. /**
  29. * The event dispatcher.
  30. *
  31. * @var \Symfony\Contracts\EventDispatcher\EventDispatcherInterface
  32. */
  33. protected $eventDispatcher;
  34. /**
  35. * Constructs a new LayoutBuilder.
  36. *
  37. * @param array $configuration
  38. * A configuration array containing information about the plugin instance.
  39. * @param string $plugin_id
  40. * The plugin ID for the plugin instance.
  41. * @param mixed $plugin_definition
  42. * The plugin implementation definition.
  43. * @param \Symfony\Contracts\EventDispatcher\EventDispatcherInterface $event_dispatcher
  44. * The event dispatcher service.
  45. * @param \Drupal\Core\Messenger\MessengerInterface|null $messenger
  46. * The messenger service. This is no longer used and will be removed in
  47. * drupal:10.0.0.
  48. */
  49. public function __construct(array $configuration, $plugin_id, $plugin_definition, $event_dispatcher, $messenger = NULL) {
  50. parent::__construct($configuration, $plugin_id, $plugin_definition);
  51. if (!($event_dispatcher instanceof EventDispatcherInterface)) {
  52. @trigger_error('The event_dispatcher service should be passed to LayoutBuilder::__construct() instead of the layout_builder.tempstore_repository service since 9.1.0. This will be required in Drupal 10.0.0. See https://www.drupal.org/node/3152690', E_USER_DEPRECATED);
  53. $event_dispatcher = \Drupal::service('event_dispatcher');
  54. }
  55. $this->eventDispatcher = $event_dispatcher;
  56. if ($messenger) {
  57. @trigger_error('Calling LayoutBuilder::__construct() with the $messenger argument is deprecated in drupal:9.1.0 and will be removed in drupal:10.0.0. See https://www.drupal.org/node/3152690', E_USER_DEPRECATED);
  58. }
  59. }
  60. /**
  61. * {@inheritdoc}
  62. */
  63. public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
  64. return new static(
  65. $configuration,
  66. $plugin_id,
  67. $plugin_definition,
  68. $container->get('event_dispatcher')
  69. );
  70. }
  71. /**
  72. * {@inheritdoc}
  73. */
  74. public function getInfo() {
  75. return [
  76. '#section_storage' => NULL,
  77. '#pre_render' => [
  78. [$this, 'preRender'],
  79. ],
  80. ];
  81. }
  82. /**
  83. * Pre-render callback: Renders the Layout Builder UI.
  84. */
  85. public function preRender($element) {
  86. if ($element['#section_storage'] instanceof SectionStorageInterface) {
  87. $element['layout_builder'] = $this->layout($element['#section_storage']);
  88. }
  89. return $element;
  90. }
  91. /**
  92. * Renders the Layout UI.
  93. *
  94. * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
  95. * The section storage.
  96. *
  97. * @return array
  98. * A render array.
  99. */
  100. protected function layout(SectionStorageInterface $section_storage) {
  101. $this->prepareLayout($section_storage);
  102. $output = [];
  103. if ($this->isAjax()) {
  104. $output['status_messages'] = [
  105. '#type' => 'status_messages',
  106. ];
  107. }
  108. $count = 0;
  109. for ($i = 0; $i < $section_storage->count(); $i++) {
  110. $output[] = $this->buildAddSectionLink($section_storage, $count);
  111. $output[] = $this->buildAdministrativeSection($section_storage, $count);
  112. $count++;
  113. }
  114. $output[] = $this->buildAddSectionLink($section_storage, $count);
  115. $output['#attached']['library'][] = 'layout_builder/drupal.layout_builder';
  116. // As the Layout Builder UI is typically displayed using the frontend theme,
  117. // it is not marked as an administrative page at the route level even though
  118. // it performs an administrative task. Mark this as an administrative page
  119. // for JavaScript.
  120. $output['#attached']['drupalSettings']['path']['currentPathIsAdmin'] = TRUE;
  121. $output['#type'] = 'container';
  122. $output['#attributes']['id'] = 'layout-builder';
  123. $output['#attributes']['class'][] = 'layout-builder';
  124. // Mark this UI as uncacheable.
  125. $output['#cache']['max-age'] = 0;
  126. return $output;
  127. }
  128. /**
  129. * Prepares a layout for use in the UI.
  130. *
  131. * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
  132. * The section storage.
  133. */
  134. protected function prepareLayout(SectionStorageInterface $section_storage) {
  135. $event = new PrepareLayoutEvent($section_storage);
  136. $this->eventDispatcher->dispatch($event, LayoutBuilderEvents::PREPARE_LAYOUT);
  137. }
  138. /**
  139. * Builds a link to add a new section at a given delta.
  140. *
  141. * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
  142. * The section storage.
  143. * @param int $delta
  144. * The delta of the section to splice.
  145. *
  146. * @return array
  147. * A render array for a link.
  148. */
  149. protected function buildAddSectionLink(SectionStorageInterface $section_storage, $delta) {
  150. $storage_type = $section_storage->getStorageType();
  151. $storage_id = $section_storage->getStorageId();
  152. // If the delta and the count are the same, it is either the end of the
  153. // layout or an empty layout.
  154. if ($delta === count($section_storage)) {
  155. if ($delta === 0) {
  156. $title = $this->t('Add section');
  157. }
  158. else {
  159. $title = $this->t('Add section <span class="visually-hidden">at end of layout</span>');
  160. }
  161. }
  162. // If the delta and the count are different, it is either the beginning of
  163. // the layout or in between two sections.
  164. else {
  165. if ($delta === 0) {
  166. $title = $this->t('Add section <span class="visually-hidden">at start of layout</span>');
  167. }
  168. else {
  169. $title = $this->t('Add section <span class="visually-hidden">between @first and @second</span>', ['@first' => $delta, '@second' => $delta + 1]);
  170. }
  171. }
  172. return [
  173. 'link' => [
  174. '#type' => 'link',
  175. '#title' => $title,
  176. '#url' => Url::fromRoute('layout_builder.choose_section',
  177. [
  178. 'section_storage_type' => $storage_type,
  179. 'section_storage' => $storage_id,
  180. 'delta' => $delta,
  181. ],
  182. [
  183. 'attributes' => [
  184. 'class' => [
  185. 'use-ajax',
  186. 'layout-builder__link',
  187. 'layout-builder__link--add',
  188. ],
  189. 'data-dialog-type' => 'dialog',
  190. 'data-dialog-renderer' => 'off_canvas',
  191. ],
  192. ]
  193. ),
  194. ],
  195. '#type' => 'container',
  196. '#attributes' => [
  197. 'class' => ['layout-builder__add-section'],
  198. 'data-layout-builder-highlight-id' => $this->sectionAddHighlightId($delta),
  199. ],
  200. ];
  201. }
  202. /**
  203. * Builds the render array for the layout section while editing.
  204. *
  205. * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
  206. * The section storage.
  207. * @param int $delta
  208. * The delta of the section.
  209. *
  210. * @return array
  211. * The render array for a given section.
  212. */
  213. protected function buildAdministrativeSection(SectionStorageInterface $section_storage, $delta) {
  214. $storage_type = $section_storage->getStorageType();
  215. $storage_id = $section_storage->getStorageId();
  216. $section = $section_storage->getSection($delta);
  217. $layout = $section->getLayout($this->getPopulatedContexts($section_storage));
  218. $layout_settings = $section->getLayoutSettings();
  219. $section_label = !empty($layout_settings['label']) ? $layout_settings['label'] : $this->t('Section @section', ['@section' => $delta + 1]);
  220. $build = $section->toRenderArray($this->getPopulatedContexts($section_storage), TRUE);
  221. $layout_definition = $layout->getPluginDefinition();
  222. $region_labels = $layout_definition->getRegionLabels();
  223. foreach ($layout_definition->getRegions() as $region => $info) {
  224. if (!empty($build[$region])) {
  225. foreach (Element::children($build[$region]) as $uuid) {
  226. $build[$region][$uuid]['#attributes']['class'][] = 'js-layout-builder-block';
  227. $build[$region][$uuid]['#attributes']['class'][] = 'layout-builder-block';
  228. $build[$region][$uuid]['#attributes']['data-layout-block-uuid'] = $uuid;
  229. $build[$region][$uuid]['#attributes']['data-layout-builder-highlight-id'] = $this->blockUpdateHighlightId($uuid);
  230. $build[$region][$uuid]['#contextual_links'] = [
  231. 'layout_builder_block' => [
  232. 'route_parameters' => [
  233. 'section_storage_type' => $storage_type,
  234. 'section_storage' => $storage_id,
  235. 'delta' => $delta,
  236. 'region' => $region,
  237. 'uuid' => $uuid,
  238. ],
  239. // Add metadata about the current operations available in
  240. // contextual links. This will invalidate the client-side cache of
  241. // links that were cached before the 'move' link was added.
  242. // @see layout_builder.links.contextual.yml
  243. 'metadata' => [
  244. 'operations' => 'move:update:remove',
  245. ],
  246. ],
  247. ];
  248. }
  249. }
  250. $build[$region]['layout_builder_add_block']['link'] = [
  251. '#type' => 'link',
  252. // Add one to the current delta since it is zero-indexed.
  253. '#title' => $this->t('Add block <span class="visually-hidden">in @section, @region region</span>', ['@section' => $section_label, '@region' => $region_labels[$region]]),
  254. '#url' => Url::fromRoute('layout_builder.choose_block',
  255. [
  256. 'section_storage_type' => $storage_type,
  257. 'section_storage' => $storage_id,
  258. 'delta' => $delta,
  259. 'region' => $region,
  260. ],
  261. [
  262. 'attributes' => [
  263. 'class' => [
  264. 'use-ajax',
  265. 'layout-builder__link',
  266. 'layout-builder__link--add',
  267. ],
  268. 'data-dialog-type' => 'dialog',
  269. 'data-dialog-renderer' => 'off_canvas',
  270. ],
  271. ]
  272. ),
  273. ];
  274. $build[$region]['layout_builder_add_block']['#type'] = 'container';
  275. $build[$region]['layout_builder_add_block']['#attributes'] = [
  276. 'class' => ['layout-builder__add-block'],
  277. 'data-layout-builder-highlight-id' => $this->blockAddHighlightId($delta, $region),
  278. ];
  279. $build[$region]['layout_builder_add_block']['#weight'] = 1000;
  280. $build[$region]['#attributes']['data-region'] = $region;
  281. $build[$region]['#attributes']['class'][] = 'layout-builder__region';
  282. $build[$region]['#attributes']['class'][] = 'js-layout-builder-region';
  283. $build[$region]['#attributes']['role'] = 'group';
  284. $build[$region]['#attributes']['aria-label'] = $this->t('@region region in @section', [
  285. '@region' => $info['label'],
  286. '@section' => $section_label,
  287. ]);
  288. // Get weights of all children for use by the region label.
  289. $weights = array_map(function ($a) {
  290. return $a['#weight'] ?? 0;
  291. }, $build[$region]);
  292. // The region label is made visible when the move block dialog is open.
  293. $build[$region]['region_label'] = [
  294. '#type' => 'container',
  295. '#attributes' => [
  296. 'class' => ['layout__region-info', 'layout-builder__region-label'],
  297. // A more detailed version of this information is already read by
  298. // screen readers, so this label can be hidden from them.
  299. 'aria-hidden' => TRUE,
  300. ],
  301. '#markup' => $this->t('Region: @region', ['@region' => $info['label']]),
  302. // Ensures the region label is displayed first.
  303. '#weight' => min($weights) - 1,
  304. ];
  305. }
  306. $build['#attributes']['data-layout-update-url'] = Url::fromRoute('layout_builder.move_block', [
  307. 'section_storage_type' => $storage_type,
  308. 'section_storage' => $storage_id,
  309. ])->toString();
  310. $build['#attributes']['data-layout-delta'] = $delta;
  311. $build['#attributes']['class'][] = 'layout-builder__layout';
  312. $build['#attributes']['data-layout-builder-highlight-id'] = $this->sectionUpdateHighlightId($delta);
  313. return [
  314. '#type' => 'container',
  315. '#attributes' => [
  316. 'class' => ['layout-builder__section'],
  317. 'role' => 'group',
  318. 'aria-label' => $section_label,
  319. ],
  320. 'remove' => [
  321. '#type' => 'link',
  322. '#title' => $this->t('Remove @section', ['@section' => $section_label]),
  323. '#url' => Url::fromRoute('layout_builder.remove_section', [
  324. 'section_storage_type' => $storage_type,
  325. 'section_storage' => $storage_id,
  326. 'delta' => $delta,
  327. ]),
  328. '#attributes' => [
  329. 'class' => [
  330. 'use-ajax',
  331. 'layout-builder__link',
  332. 'layout-builder__link--remove',
  333. ],
  334. 'data-dialog-type' => 'dialog',
  335. 'data-dialog-renderer' => 'off_canvas',
  336. ],
  337. ],
  338. // The section label is added to sections without a "Configure section"
  339. // link, and is only visible when the move block dialog is open.
  340. 'section_label' => [
  341. '#markup' => $this->t('<span class="layout-builder__section-label" aria-hidden="true">@section</span>', ['@section' => $section_label]),
  342. '#access' => !$layout instanceof PluginFormInterface,
  343. ],
  344. 'configure' => [
  345. '#type' => 'link',
  346. '#title' => $this->t('Configure @section', ['@section' => $section_label]),
  347. '#access' => $layout instanceof PluginFormInterface,
  348. '#url' => Url::fromRoute('layout_builder.configure_section', [
  349. 'section_storage_type' => $storage_type,
  350. 'section_storage' => $storage_id,
  351. 'delta' => $delta,
  352. ]),
  353. '#attributes' => [
  354. 'class' => [
  355. 'use-ajax',
  356. 'layout-builder__link',
  357. 'layout-builder__link--configure',
  358. ],
  359. 'data-dialog-type' => 'dialog',
  360. 'data-dialog-renderer' => 'off_canvas',
  361. ],
  362. ],
  363. 'layout-builder__section' => $build,
  364. ];
  365. }
  366. }