From 2019eaecdaa572fdc0bcf2ad06de2b1757498547 Mon Sep 17 00:00:00 2001 From: Elliot Mitchum Date: Sun, 24 Jul 2016 20:15:24 +1000 Subject: [PATCH] Integrate Context Layout submodule port --- .../context_layout/context_layout.info.yml | 8 + .../context_layout.links.menu.yml | 5 + .../context_layout.links.task.yml | 5 + .../context_layout/context_layout.routing.yml | 53 ++ .../context_layout.services.yml | 9 + .../LayoutPageDisplayVariantSubscriber.php | 67 +++ .../context_layout/src/Form/SettingsForm.php | 100 ++++ .../ContextLayout/ContextLayoutManager.php | 109 ++++ .../src/Plugin/ContextReaction/Layouts.php | 560 ++++++++++++++++++ .../ContextLayoutPageVariant.php | 120 ++++ .../ContextReactionLayoutsController.php | 179 ++++++ .../Layouts/Form/LayoutBlockAddForm.php | 31 + .../Layouts/Form/LayoutBlockDeleteForm.php | 50 ++ .../Layouts/Form/LayoutBlockEditForm.php | 31 + .../Layouts/Form/LayoutBlockFormBase.php | 141 +++++ 15 files changed, 1468 insertions(+) create mode 100644 modules/context_layout/context_layout.info.yml create mode 100644 modules/context_layout/context_layout.links.menu.yml create mode 100644 modules/context_layout/context_layout.links.task.yml create mode 100644 modules/context_layout/context_layout.routing.yml create mode 100644 modules/context_layout/context_layout.services.yml create mode 100644 modules/context_layout/src/EventSubscriber/LayoutPageDisplayVariantSubscriber.php create mode 100644 modules/context_layout/src/Form/SettingsForm.php create mode 100644 modules/context_layout/src/Plugin/ContextLayout/ContextLayoutManager.php create mode 100644 modules/context_layout/src/Plugin/ContextReaction/Layouts.php create mode 100644 modules/context_layout/src/Plugin/DisplayVariant/ContextLayoutPageVariant.php create mode 100644 modules/context_layout/src/Reaction/Layouts/Controller/ContextReactionLayoutsController.php create mode 100644 modules/context_layout/src/Reaction/Layouts/Form/LayoutBlockAddForm.php create mode 100644 modules/context_layout/src/Reaction/Layouts/Form/LayoutBlockDeleteForm.php create mode 100644 modules/context_layout/src/Reaction/Layouts/Form/LayoutBlockEditForm.php create mode 100644 modules/context_layout/src/Reaction/Layouts/Form/LayoutBlockFormBase.php diff --git a/modules/context_layout/context_layout.info.yml b/modules/context_layout/context_layout.info.yml new file mode 100644 index 0000000..66c3242 --- /dev/null +++ b/modules/context_layout/context_layout.info.yml @@ -0,0 +1,8 @@ +name: Context Layout +type: module +description: 'Block Page Reaction (with Layout plugin integration)' +core: 8.x +package: Context +dependencies: + - context + - layout_plugin \ No newline at end of file diff --git a/modules/context_layout/context_layout.links.menu.yml b/modules/context_layout/context_layout.links.menu.yml new file mode 100644 index 0000000..c927363 --- /dev/null +++ b/modules/context_layout/context_layout.links.menu.yml @@ -0,0 +1,5 @@ +context_layout.admin_settings: + title: 'Context Layout settings' + description: 'Configure context layout reactions.' + route_name: context_layout.admin_settings + parent: 'system.admin_config_development' \ No newline at end of file diff --git a/modules/context_layout/context_layout.links.task.yml b/modules/context_layout/context_layout.links.task.yml new file mode 100644 index 0000000..e866bf6 --- /dev/null +++ b/modules/context_layout/context_layout.links.task.yml @@ -0,0 +1,5 @@ +context_layout.admin_settings: + title: 'Settings' + route_name: context_layout.admin_settings + base_route: context_layout.admin_settings + weight: 0 \ No newline at end of file diff --git a/modules/context_layout/context_layout.routing.yml b/modules/context_layout/context_layout.routing.yml new file mode 100644 index 0000000..81c4c8a --- /dev/null +++ b/modules/context_layout/context_layout.routing.yml @@ -0,0 +1,53 @@ +context_layout.admin_settings: + path: '/admin/config/development/context_layout' + defaults: + _form: '\Drupal\context_layout\Form\SettingsForm' + _title: 'Context Layout' + requirements: + _permission: 'administer site configuration' + +context.reaction.layouts.theme_select: + path: '/admin/structure/context/{context}/reaction/layouts/theme' + defaults: + _controller: '\Drupal\context_layout\Reaction\Layouts\Controller\ContextReactionLayoutsController::themeSelect' + requirements: + _entity_access: 'context.update' + +context.reaction.layouts.layout_select: + path: '/admin/structure/context/{context}/reaction/layouts/layout' + defaults: + _controller: '\Drupal\context_layout\Reaction\Layouts\Controller\ContextReactionLayoutsController::layoutSelect' + requirements: + _permission: 'administer contexts' + +context.reaction.layouts.library: + path: '/admin/structure/context/{context}/reaction/layouts/{reaction_id}/library' + defaults: + _title: 'Add block' + _controller: '\Drupal\context_layout\Reaction\Layouts\Controller\ContextReactionLayoutsController::blocksLibrary' + requirements: + _entity_access: 'context.update' + +context.reaction.layouts.block_delete: + path: '/admin/structure/context/{context}/reaction/layouts/delete/{block_id}' + defaults: + _form: '\Drupal\context_layout\Reaction\Layouts\Form\LayoutBlockDeleteForm' + _title: 'Delete block' + requirements: + _entity_access: 'context.update' + +context.reaction.layouts.block_add: + path: '/admin/structure/context/{context}/reaction/layouts/{reaction_id}/add/{block_id}' + defaults: + _form: '\Drupal\context_layout\Reaction\Layouts\Form\LayoutBlockAddForm' + _title: 'Add block' + requirements: + _entity_access: 'context.update' + +context.reaction.layouts.block_edit: + path: '/admin/structure/context/{context}/reaction/layouts/{reaction_id}/edit/{block_id}' + defaults: + _form: '\Drupal\context_layout\Reaction\Layouts\Form\LayoutBlockEditForm' + _title: 'Edit block' + requirements: + _entity_access: 'context.update' \ No newline at end of file diff --git a/modules/context_layout/context_layout.services.yml b/modules/context_layout/context_layout.services.yml new file mode 100644 index 0000000..a46843f --- /dev/null +++ b/modules/context_layout/context_layout.services.yml @@ -0,0 +1,9 @@ +services: + context_layout.layout_page_display_variant_subscriber: + class: Drupal\context_layout\EventSubscriber\LayoutPageDisplayVariantSubscriber + arguments: ['@context.manager'] + tags: + - { name: event_subscriber } + plugin.manager.context_layout: + class: Drupal\context_layout\Plugin\ContextLayout\ContextLayoutManager + arguments: ['@container.namespaces', '@cache.discovery', '@module_handler', '@theme_handler'] \ No newline at end of file diff --git a/modules/context_layout/src/EventSubscriber/LayoutPageDisplayVariantSubscriber.php b/modules/context_layout/src/EventSubscriber/LayoutPageDisplayVariantSubscriber.php new file mode 100644 index 0000000..39c6170 --- /dev/null +++ b/modules/context_layout/src/EventSubscriber/LayoutPageDisplayVariantSubscriber.php @@ -0,0 +1,67 @@ +contextManager = $contextManager; + } + + /** + * Selects the context layout page display variant. + * + * @param \Drupal\Core\Render\PageDisplayVariantSelectionEvent $event + * The event to process. + */ + public function onSelectPageDisplayVariant(PageDisplayVariantSelectionEvent $event) { + // Exit if on admin route and context_layout.settings.admin_allow is true. + $admin = \Drupal::service('router.admin_context')->isAdminRoute(); + $allow = \Drupal::config('context_layout.settings') + ->get('admin_allow'); + if ($admin && !$allow) { + return; + } + // Activate the context block page display variant if any of the reactions + // is a blocks reaction. + foreach ($this->contextManager->getActiveReactions() as $reaction) { + if ($reaction instanceof Layouts) { + $event->setPluginId('context_layout_page'); + break; + } + } + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + $events[RenderEvents::SELECT_PAGE_DISPLAY_VARIANT][] = array('onSelectPageDisplayVariant'); + return $events; + } + +} diff --git a/modules/context_layout/src/Form/SettingsForm.php b/modules/context_layout/src/Form/SettingsForm.php new file mode 100644 index 0000000..599aa53 --- /dev/null +++ b/modules/context_layout/src/Form/SettingsForm.php @@ -0,0 +1,100 @@ +contextLayoutManager = $contextLayoutManager; + parent::__construct($config_factory); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'context_layout_settings'; + } + + /** + * Inject dependencies to constructor. + * + * @param \Symfony\Component\DependencyInjection\ContainerInterface $container + * The current service container. + * + * @return static + * Dependencies for injection. + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('plugin.manager.context_layout'), + $container->get('config.factory') + ); + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + // Module settings. + $config = $this->config('context_layout.settings'); + + // Default layout for layout reactions. + $form['default_layout'] = [ + '#title' => t('Default Layout'), + '#description' => t('Select a default layout for layout reactions'), + '#type' => 'select', + '#options' => [0 => '---'] + $this->contextLayoutManager->getLayoutOptions( + ['group_by_category' => TRUE] + ), + '#default_value' => $config->get('default_layout'), + ]; + + // Exclude layout displays on admin routes. + $form['admin_allow'] = [ + '#title' => t('Allow Admin Routes'), + '#description' => t('Check to allow Context Layout reactions on admin routes, eg: admin/structure.'), + '#type' => 'checkbox', + '#default_value' => $config->get('admin_allow'), + ]; + + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $values = $form_state->getValues(); + $this->config('context_layout.settings') + ->set('default_layout', $values['default_layout']) + ->set('admin_allow', $values['admin_allow']) + ->save(); + parent::submitForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + protected function getEditableConfigNames() { + return ['context_layout.settings']; + } + +} diff --git a/modules/context_layout/src/Plugin/ContextLayout/ContextLayoutManager.php b/modules/context_layout/src/Plugin/ContextLayout/ContextLayoutManager.php new file mode 100644 index 0000000..a83bbdd --- /dev/null +++ b/modules/context_layout/src/Plugin/ContextLayout/ContextLayoutManager.php @@ -0,0 +1,109 @@ +getDefinitions(); + $options = array(); + // Sort the plugins first by category, then by label. + foreach ($plugins as $id => $plugin) { + // Only layouts of type 'full' are allowed. + if ($type != $plugin['type']) { + continue; + } + if ($group_by_category) { + $category = isset($plugin['category']) ? (string) $plugin['category'] : 'default'; + if (!isset($options[$category])) { + $options[$category] = array(); + } + $options[$category][$id] = $plugin['label']; + } + else { + $options[$id] = $plugin['label']; + } + } + return $options; + } + + /** + * Returns a Drupal\layout_plugin\Layout instance. + * + * @param string $layout + * Layout ID (machine name). + * @param bool|false $fallback + * Whether to return a fallback layout if default doesn't exist. + * + * @return object + * Drupal\layout_plugin\Layout instance. + */ + public function loadLayout($layout, $fallback = FALSE) { + // We want to return the correct layout if 'default' is passed. + if ('default' == $layout) { + $layout = $this->createInstance($this->getDefaultLayout($fallback)); + } + else { + $layout = $this->createInstance($layout); + } + return $layout; + } + + /** + * Returns default Drupal\layout_plugin\Layout instance. + * + * @param bool|false $fallback + * Whether to return a fallback layout if default doesn't exist. + * + * @return string + * Layout ID (machine name). + */ + public function getDefaultLayout($fallback = FALSE) { + $layout = \Drupal::config('context_layout.settings') + ->get('default_layout'); + if ($fallback && !$layout) { + // Get the first available layout. + $layout = array_keys( + $this->getLayoutOptions() + )[0]; + } + return $layout; + } + + /** + * Return available layout regions. + * + * @param array $regions + * Region ID's. + * @param string $layout_id + * Layout ID (machine name). + * + * @return array + * Available layout region ID's. + */ + public function filterLayoutRegions($regions, $layout_id) { + $layout = \Drupal::service('plugin.manager.context_layout') + ->loadLayout($layout_id); + $layout_regions = array_keys($layout->getRegionDefinitions()); + foreach ($regions as $region_id => $region) { + if (!in_array($region_id, $layout_regions)) { + unset($regions[$region_id]); + } + } + return $regions; + } + +} diff --git a/modules/context_layout/src/Plugin/ContextReaction/Layouts.php b/modules/context_layout/src/Plugin/ContextReaction/Layouts.php new file mode 100644 index 0000000..2551c69 --- /dev/null +++ b/modules/context_layout/src/Plugin/ContextReaction/Layouts.php @@ -0,0 +1,560 @@ +themeHandler = $themeHandler; + $this->contextRepository = $contextRepository; + $this->contextHandler = $contextHandler; + $this->themeManager = $themeManager; + $this->contextLayoutManager = $contextLayoutManager; + $this->contextReactionManager = $contextReactionManager; + // Create a Drupal\context\Plugin\ContextReaction\Blocks instance. + $this->blocks = $contextReactionManager->createInstance('blocks'); + parent::__construct($configuration, $pluginId, $pluginDefinition); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $pluginId, $pluginDefinition) { + return new static( + $configuration, + $pluginId, + $pluginDefinition, + $container->get('theme_handler'), + $container->get('theme.manager'), + $container->get('context.repository'), + $container->get('context.handler'), + $container->get('plugin.manager.context_layout'), + $container->get('plugin.manager.context_reaction') + ); + } + + /** + * Executes the plugin. + * + * @param array $build + * The current build of the page. + * + * @param string|null $title + * The page title. + * + * @param string|null $main_content + * The main page content. + * + * @return array + */ + public function execute(array $build = array(), $title = NULL, $main_content = NULL) { + // Check for an existing context layout from page build. + if (isset($build['#layout'])) { + $layout = $build['#layout']['id']; + $layout_regions = $this->contextLayoutManager + ->loadLayout($build['#layout']['id']) + ->getRegionDefinitions(); + } + else { + $layout = $this->layout; + $layout_regions = $this->contextLayoutManager + ->loadLayout($this->layout) + ->getRegionDefinitions($this->layout); + } + $layout_regions = array_keys($layout_regions); + + // Use the currently active theme to fetch blocks. + $theme = $this->themeManager->getActiveTheme()->getName(); + $theme_regions = array_keys(system_region_list($theme)); + + // Get Blocks context reaction build array. + $build = $this->blocks->execute($build, $title, $main_content); + + // Remove blocks that are not available in layout's regions. + foreach ($build as $key => $value) { + $in_theme = in_array($key, $theme_regions); + $in_layout = in_array($key, $layout_regions); + if ($in_theme && !$in_layout) { + unset($build[$key]); + } + } + + /** @var $layout \Drupal\layout_plugin\Layout */ + $layout = $this->contextLayoutManager->loadLayout($layout); + return $layout->build($build); + } + + /** + * {@inheritdoc} + */ + public function summary() { + return $this->t('Lets you select a layout and blocks.'); + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state, ContextInterface $context = NULL) { + // Check for available layouts and display appropriate form. + $options = $this->getLayoutOptions(); + if (!$options) { + return $this->invalidConfigurationForm($form); + } + else { + return $this->validConfigurationForm($form, $form_state, $context); + } + } + + /** + * Display invalid build form. + * + * @param array $form + * An associative array containing the structure of the form. + * + * @return array + * Modified $form. + */ + private function invalidConfigurationForm(array $form) { + $form['no_layouts'] = [ + '#type' => 'item', + '#title' => t('No Defined Layouts'), + '#description' => t('You have no defined \'full\' type layouts. You + must register layouts with the type of \'full\' within your theme. ') . + Link::fromTextAndUrl( + 'See Layout Plugin documentation', + Url::fromUri('https://www.drupal.org/node/2578731'))->toString() + ]; + return $form; + } + + /** + * Display valid configuration build form. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * @param \Drupal\context\ContextInterface|null $context + * Current context. + * + * @return array + * Modified $form. + */ + private function validConfigurationForm(array $form, FormStateInterface $form_state, ContextInterface $context = NULL) { + $layout = $form_state->getValue('layout', $this->getLayout()); + + // The layout to use for the context reaction. + $form['layout'] = [ + '#type' => 'select', + '#title' => t('Layout'), + '#options' => $this->getLayoutOptions([ + 'group_by_category' => TRUE, + 'default' => TRUE, + ]), + '#default_value' => $layout, + '#ajax' => [ + 'url' => Url::fromRoute('context.reaction.layouts.layout_select', [ + 'context' => $context->id(), + ]), + ], + ]; + + // Build blocks reaction build configuration form. + $form += $this->blocks->buildConfigurationForm($form, $form_state, $context); + + // Filter the blocks reaction build configuration form. + $this->filterForm($form, $form_state, $context); + + return $form; + } + + /** + * Get layout regions from collection of blocks. + * + * @param string $theme + * The theme to get blocks for. + * @param string $layout + * Layout ID (machine name). + * + * @return array + * Region ID's. + */ + private function getUsedLayoutRegions($theme, $layout) { + $layout_regions = []; + $regions = $this->blocks->getBlocks()->getAllByRegion($theme); + foreach ($regions as $region_id => $region) { + foreach ($region as $theme_block) { + if ($layout == $theme_block->configuration['layout']) { + $layout_regions[] = $region_id; + } + } + } + return $layout_regions; + } + + /** + * Filter blocks reaction build configuration form. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * @param \Drupal\context\ContextInterface|null $context + * Current context. + * + * @return array + * Modified $form. + */ + private function filterForm(array &$form, FormStateInterface &$form_state, ContextInterface $context) { + $layout = $form_state->getValue('layout', $this->getLayout()); + $default_theme = $this->themeHandler->getDefault(); + $theme = $form_state->getValue('theme', $default_theme); + $system_regions = system_region_list($theme); + + /** @var \Drupal\Core\Block\BlockPluginInterface $blocks */ + $blocks = $this->blocks->getBlocks()->getAllByRegion($theme); + + // Get layout region ID's. + $layout_regions = array_keys( + $this->contextLayoutManager->loadLayout($layout) + ->getRegionDefinitions() + ); + + $used_layout_regions = $this->getUsedLayoutRegions($theme, $layout); + + // Filter theme selection route. + $form['theme']['#ajax']['url'] = Url::fromRoute( + 'context.reaction.layouts.theme_select', [ + 'context' => $context->id(), + ] + ); + + // Filter block library route. + $form['blocks']['block_add']['#url'] = Url::fromRoute( + 'context.reaction.layouts.library', [ + 'context' => $context->id(), + 'reaction_id' => $this->getPluginId(), + ], [ + 'query' => [ + 'theme' => $theme, + 'layout' => $layout, + ], + ] + ); + + // Tabledrag row count. + $table_count = 0; + foreach ($system_regions as $region => $title) { + + // Remove regions unavailable to layout. + $region_id = 'region-' . $region; + $region_message_id = $region_id . '-message'; + $layout_has_region = in_array($region, $layout_regions); + $layout_used_region = in_array($region, $used_layout_regions); + + if (!$layout_has_region) { + unset($form['blocks']['blocks'][$region_message_id]); + unset($form['blocks']['blocks'][$region_id]); + } + + // Apply empty classes to tabledrag rows that may now be empty. + if ($layout_has_region && !$layout_used_region) { + if (isset($form['blocks']['blocks'][$region_message_id])) { + foreach ($form['blocks']['blocks'][$region_message_id]['#attributes']['class'] as $i => $class) { + if ('region-populated' == $class) { + unset($form['blocks']['blocks'][$region_message_id]['#attributes']['class'][$i]); + $form['blocks']['blocks'][$region_message_id]['#attributes']['class'][] = 'region-empty'; + } + } + } + } + + // Remove tabledrag rows unavailable to layout. + if (!in_array($region, $layout_regions)) { + unset($form['blocks']['blocks']['#tabledrag'][$table_count]); + unset($form['blocks']['blocks']['#tabledrag'][$table_count + 1]); + } + + // Tabledrag has 2 rows per region. + $table_count += 2; + + if (isset($blocks[$region])) { + + // Loop through blocks within the current region. + foreach ($blocks[$region] as $block_id => $block) { + + // Route args for block's operations. + $url_args = [ + 'context' => $context->id(), + 'reaction_id' => $this->getPluginId(), + 'block_id' => $block_id, + ]; + + // Filter block edit route. + $form['blocks']['blocks'][$block_id]['operations']['#links']['edit']['url'] = Url::fromRoute( + 'context.reaction.layouts.block_edit', + $url_args + ); + + // Filter block delete route. + $form['blocks']['blocks'][$block_id]['operations']['#links']['delete']['url'] = Url::fromRoute( + 'context.reaction.layouts.block_delete', + $url_args + ); + + // Remove blocks that are in a region unavailable to layout. + if (!in_array($region, $layout_regions)) { + unset($form['blocks']['blocks'][$block_id]); + } + } + } + } + + // Remove block instances that are not in the current layout. + foreach ($blocks as $region_id => $region_blocks) { + foreach ($region_blocks as $block_id => $block) { + if ($layout != $block->configuration['layout']) { + unset($form['blocks']['blocks'][$block_id]); + } + } + } + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + $configuration = parent::defaultConfiguration(); + $configuration += $this->blocks->defaultConfiguration(); + $configuration['layout'] = NULL; + return $configuration; + } + + /** + * {@inheritdoc} + */ + public function setConfiguration(array $configuration) { + $this->blocks->setConfiguration($configuration); + $this->configuration = $configuration + $this->defaultConfiguration(); + if (isset($configuration['layout'])) { + $this->layout = $configuration['layout']; + } + return $this; + } + + /** + * {@inheritdoc} + */ + public function getConfiguration() { + $configuration = $this->blocks->getConfiguration(); + // Restore ID to configuration. + $configuration['id'] = 'layouts'; + $configuration['layout'] = $this->configuration['layout']; + $configuration += parent::getConfiguration(); + return $configuration; + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + $this->blocks->submitConfigurationForm($form, $form_state); + $this->configuration['layout'] = $form_state->getValue('layout'); + } + + /** + * Get most appropriate layout. + * + * @return string + * Layout ID (machine name). + */ + protected function getLayout() { + // Current layout. + if ($this->layout) { + return $this->layout; + } + // Default layout. + elseif ($this->contextLayoutManager->getDefaultLayout()) { + return 'default'; + } + // Fallback layout. + else { + $fallback = TRUE; + return $this->contextLayoutManager->getDefaultLayout($fallback); + } + } + + /** + * Return formatted layout region options. + * + * @param array $params + * (optional) An associative array with the following keys: + * - group_by_category: (bool) If set to TRUE, return an array of arrays + * grouped by the category name; otherwise, return a single-level + * associative array. + * + * @return array + * Layout options, as array. + */ + protected function getLayoutOptions(array $params = []) { + $default = !empty($params['default']); + $layouts = $this->contextLayoutManager->getLayoutOptions($params); + // Prepend default layout option. + if ($default && $this->contextLayoutManager->getDefaultLayout()) { + $default_layout = $this->contextLayoutManager->createInstance( + $this->contextLayoutManager->getDefaultLayout() + ); + $label = $default_layout->getPluginDefinition()['label'] . ' (' . t('Default') . ')'; + $layouts = ['default' => $label] + $layouts; + } + return $layouts; + } + + /** + * Add a new block. + * + * @param array $configuration + * Reaction configuration. + * + * @return string + * Generated UUID. + */ + public function addBlock(array $configuration) { + return $this->blocks->addBlock($configuration); + } + + /** + * Update an existing blocks configuration. + * + * @param string $blockId + * The ID of the block to update. + * + * @param $configuration + * The updated configuration for the block. + * + * @return object + * $this. + */ + public function updateBlock($blockId, array $configuration) { + return $this->blocks->updateBlock($blockId, $configuration); + } + + /** + * Remove a block from block instances. + * + * @param string $blockId + * The ID of the block to get. + * + * @return object + * $this. + */ + public function removeBlock($blockId) { + return $this->blocks->removeBlock($blockId); + } + + /** + * Get a block by id. + * + * @param string $blockId + * The ID of the block to get. + * + * @return object + * BlockPluginInterface instance. + */ + public function getBlock($blockId) { + return $this->blocks->getBlock($blockId); + } + +} diff --git a/modules/context_layout/src/Plugin/DisplayVariant/ContextLayoutPageVariant.php b/modules/context_layout/src/Plugin/DisplayVariant/ContextLayoutPageVariant.php new file mode 100644 index 0000000..a3aeb12 --- /dev/null +++ b/modules/context_layout/src/Plugin/DisplayVariant/ContextLayoutPageVariant.php @@ -0,0 +1,120 @@ +contextManager = $contextManager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('context.manager') + ); + } + + /** + * {@inheritdoc} + */ + public function setMainContent(array $main_content) { + $this->mainContent = $main_content; + return $this; + } + + /** + * {@inheritdoc} + */ + public function setTitle($title) { + $this->title = $title; + return $this; + } + + /** + * {@inheritdoc} + */ + public function build() { + $build = [ + '#cache' => [ + 'tags' => ['context_layout_page', $this->getPluginId()], + ], + ]; + + // Place main content and messages blocks, these will be removed by the + // reactions if a message or content block has been manually placed. + $build['content']['system_main'] = $this->mainContent; + + $build['content']['messages'] = [ + '#weight' => -1000, + '#type' => 'status_messages', + ]; + + // Execute each block reaction and let them modify the page build. + foreach ($this->contextManager->getActiveReactions('layouts') as $reaction) { + $build = $reaction->execute($build, $this->title, $this->mainContent); + } + + return $build; + } + +} diff --git a/modules/context_layout/src/Reaction/Layouts/Controller/ContextReactionLayoutsController.php b/modules/context_layout/src/Reaction/Layouts/Controller/ContextReactionLayoutsController.php new file mode 100644 index 0000000..fb5269a --- /dev/null +++ b/modules/context_layout/src/Reaction/Layouts/Controller/ContextReactionLayoutsController.php @@ -0,0 +1,179 @@ +contextLayoutManager = $contextLayoutManager; + return parent::__construct($blockManager, $contextRepository, $themeHandler, $contextManager); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('plugin.manager.block'), + $container->get('context.repository'), + $container->get('theme_handler'), + $container->get('context.manager'), + $container->get('plugin.manager.context_layout') + ); + } + + /** + * Display a library of blocks that can be added to the context reaction. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The request object. + * + * @param \Drupal\context\ContextInterface $context + * The context the blocks reaction belongs to. + * + * @param string $reaction_id + * The ID of the blocks reaction that the selected block + * should be added to. + * + * @return array + * Build array of block library. + */ + public function blocksLibrary(Request $request, ContextInterface $context, $reaction_id) { + // If a theme has been defined in the query string then use this for + // the add block link, default back to the default theme. + $theme = $request->query->get('theme'); + + // If a layout has been defined in the query string then use this for + // the add block link, default back to the default layout. + $fallback = TRUE; + $layout = $request->query->get('layout', $this->contextLayoutManager->getDefaultLayout($fallback)); + + // Build ContextReactionBlocksController block library. + $build = parent::blocksLibrary($request, $context, $reaction_id); + + foreach ($build['blocks']['#rows'] as &$row) { + // Filter block operation routes. + $url = $row['operations']['data']['#links']['add']['url']; + $params = $url->getRouteParameters(); + // Filter block add route. + $new_url = Url::fromRoute('context.reaction.layouts.block_add', [ + 'context' => $params['context'], + 'reaction_id' => $params['reaction_id'], + 'block_id' => $params['block_id'], + ], [ + 'query' => [ + 'theme' => $theme, + 'layout' => $layout, + ], + ] + ); + $row['operations']['data']['#links']['add']['url'] = $new_url; + } + + return $build; + } + + /** + * Callback for the theme select list on the Context layouts reaction form. + * + * @param Request $request + * The current request. + * + * @param ContextInterface $context + * The context the block reaction is located on. + * + * @return \Drupal\Core\Ajax\AjaxResponse + * AJAX Response instance. + */ + public function themeSelect(Request $request, ContextInterface $context) { + // Get the context form and supply it with the layouts theme value. + $theme = $request->request->get('reactions[layouts][theme]', '', TRUE); + // Get the context form and supply it with the layouts layout value. + $layout = $request->request->get('reactions[layouts][layout]', '', TRUE); + $form = $this->contextManager->getForm($context, 'edit', [ + 'reactions' => [ + 'layouts' => [ + 'theme' => $theme, + 'layout' => $layout, + ], + ], + ]); + $response = new AjaxResponse(); + $response->addCommand(new ReplaceCommand('#context-reactions', $form['reactions'])); + return $response; + } + + /** + * Callback for the theme select list on the Context layouts reaction form. + * + * @param Request $request + * The current request. + * + * @param ContextInterface $context + * The context the block reaction is located on. + * + * @return \Drupal\Core\Ajax\AjaxResponse + * AJAX Response instance. + */ + public function layoutSelect(Request $request, ContextInterface $context) { + // Get the context form and supply it with the layouts theme value. + $theme = $request->request->get('reactions[layouts][theme]', '', TRUE); + // Get the context form and supply it with the layouts layout value. + $layout = $request->request->get('reactions[layouts][layout]', '', TRUE); + $form = $this->contextManager->getForm($context, 'edit', [ + 'reactions' => [ + 'layouts' => [ + 'layout' => $layout, + 'theme' => $theme, + ], + ], + ]); + $response = new AjaxResponse(); + $response->addCommand(new ReplaceCommand('#context-reactions', $form['reactions'])); + return $response; + } + +} diff --git a/modules/context_layout/src/Reaction/Layouts/Form/LayoutBlockAddForm.php b/modules/context_layout/src/Reaction/Layouts/Form/LayoutBlockAddForm.php new file mode 100644 index 0000000..6de88a5 --- /dev/null +++ b/modules/context_layout/src/Reaction/Layouts/Form/LayoutBlockAddForm.php @@ -0,0 +1,31 @@ +t('Add block'); + } + + /** + * {@inheritdoc} + */ + protected function prepareBlock($block_id) { + return $this->blockManager->createInstance($block_id); + } + +} diff --git a/modules/context_layout/src/Reaction/Layouts/Form/LayoutBlockDeleteForm.php b/modules/context_layout/src/Reaction/Layouts/Form/LayoutBlockDeleteForm.php new file mode 100644 index 0000000..9c76323 --- /dev/null +++ b/modules/context_layout/src/Reaction/Layouts/Form/LayoutBlockDeleteForm.php @@ -0,0 +1,50 @@ +context = $context; + $this->reaction = $this->context->getReaction('layouts'); + $this->block = $this->reaction->getBlock($block_id); + + // Build Drupal\Core\Form\ConfirmFormBase form. + $form = ConfirmFormBase::buildForm($form, $form_state); + + // Remove the cancel button if this is an AJAX request since Drupals built + // in modal dialogues does not handle buttons that are not a primary + // button very well. + if ($this->getRequest()->isXmlHttpRequest()) { + unset($form['actions']['cancel']); + } + + // Submit the form with AJAX if possible. + $form['actions']['submit']['#ajax'] = [ + 'callback' => '::submitFormAjax', + ]; + + return $form; + } + +} diff --git a/modules/context_layout/src/Reaction/Layouts/Form/LayoutBlockEditForm.php b/modules/context_layout/src/Reaction/Layouts/Form/LayoutBlockEditForm.php new file mode 100644 index 0000000..ecbee7f --- /dev/null +++ b/modules/context_layout/src/Reaction/Layouts/Form/LayoutBlockEditForm.php @@ -0,0 +1,31 @@ +t('Update block'); + } + + /** + * {@inheritdoc} + */ + protected function prepareBlock($block_id) { + return $this->reaction->getBlock($block_id); + } + +} diff --git a/modules/context_layout/src/Reaction/Layouts/Form/LayoutBlockFormBase.php b/modules/context_layout/src/Reaction/Layouts/Form/LayoutBlockFormBase.php new file mode 100644 index 0000000..12558cc --- /dev/null +++ b/modules/context_layout/src/Reaction/Layouts/Form/LayoutBlockFormBase.php @@ -0,0 +1,141 @@ +contextLayoutManager = $contextLayoutManager; + parent::__construct($block_manager, $contextRepository, $themeHandler, $formBuilder, $contextReactionManager, $contextManager); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('plugin.manager.block'), + $container->get('context.repository'), + $container->get('theme_handler'), + $container->get('form_builder'), + $container->get('plugin.manager.context_reaction'), + $container->get('context.manager'), + $container->get('plugin.manager.context_layout') + ); + } + + /** + * Form constructor. + * + * @param array $form + * An associative array containing the structure of the form. + * + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @param ContextInterface $context + * The context the reaction belongs to. + * + * @param string|null $reaction_id + * The ID of the blocks reaction the block should be added to. + * + * @param string|null $block_id + * The ID of the block to show a configuration form for. + * + * @return array + * Build form structure. + */ + public function buildForm(array $form, FormStateInterface $form_state, ContextInterface $context = NULL, $reaction_id = NULL, $block_id = NULL) { + // Submit BlockFormBase build form. + $form = parent::buildForm($form, $form_state, $context, $reaction_id, $block_id); + + // If a theme was defined in the query use this theme for the block + // otherwise use the default theme. + $theme = $this->getRequest()->query->get('theme', $this->themeHandler->getDefault()); + + $configuration = $this->block->getConfiguration(); + $regions = $this->getThemeRegionOptions($theme); + + // Get layout from either request or configuration. + if ($this->getRequest()->query->get('layout') || $configuration['layout']) { + $layout = $configuration['layout']; + // Layout from request takes precedence. + if ($this->getRequest()->query->get('layout')) { + $layout = $this->getRequest()->query->get('layout'); + } + $regions = $this->contextLayoutManager->filterLayoutRegions( + $this->getThemeRegionOptions($theme), + $layout + ); + } + else { + $fallback = TRUE; + $layout = $this->contextLayoutManager->getDefaultLayout($fallback); + } + + // Remove regions unavailable to layout. + $form['region']['#options'] = $regions; + + $form['layout'] = [ + '#type' => 'value', + '#value' => $layout, + ]; + + return $form; + } + + /** + * Form submission handler. + * + * @param array $form + * An associative array containing the structure of the form. + * + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $configuration = array_merge($this->block->getConfiguration(), [ + 'layout' => $form_state->getValue('layout'), + ]); + // Set layout value in block configuration. + $this->block->setConfiguration($configuration); + // Let BlockFormBase do the rest. + parent::submitForm($form, $form_state); + } + +}