diff --git a/panelizer.routing.yml b/panelizer.routing.yml index 5240094..98bf6fa 100644 --- a/panelizer.routing.yml +++ b/panelizer.routing.yml @@ -59,3 +59,40 @@ panelizer.wizard.step.context.delete: tempstore_id: 'panelizer.wizard' requirements: _permission: 'administer pages' + +# Content blocks +panelizer.wizard.step.content.select_block: + path: '/admin/panelizer/wizard/block_display/{block_display}/select' + defaults: + _controller: '\Drupal\panelizer\Controller\PanelizerWizardController::selectBlock' + _title: 'Select block' + tempstore_id: 'panelizer.block_display' + requirements: + _permission: 'administer pages' + +panelizer.wizard.step.content.add_block: + path: '/admin/panelizer/wizard/block_display/{block_display}/add/{block_id}' + defaults: + _form: '\Drupal\panelizer\Form\PanelizerWizardAddBlockForm' + _title: 'Add block' + tempstore_id: 'panelizer.block_display' + requirements: + _permission: 'administer pages' + +panelizer.wizard.step.content.edit_block: + path: '/admin/panelizer/wizard/block_display/{block_display}/edit/{block_id}' + defaults: + _form: '\Drupal\panelizer\Form\PanelizerWizardEditBlockForm' + _title: 'Edit block' + tempstore_id: 'panelizer.block_display' + requirements: + _permission: 'administer pages' + +panelizer.wizard.step.content.delete_block: + path: '/admin/panelizer/wizard/block_display/{block_display}/delete/{block_id}' + defaults: + _form: '\Drupal\panelizer\Form\PanelizerWizardDeleteBlockForm' + _title: 'Delete block' + tempstore_id: 'panelizer.block_display' + requirements: + _permission: 'administer pages' diff --git a/src/Controller/PanelizerWizardController.php b/src/Controller/PanelizerWizardController.php new file mode 100644 index 0000000..3f6364d --- /dev/null +++ b/src/Controller/PanelizerWizardController.php @@ -0,0 +1,167 @@ +blockManager = $block_manager; + $this->conditionManager = $condition_manager; + $this->variantManager = $variant_manager; + $this->contextHandler = $context_handler; + $this->tempstore = $tempstore; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('plugin.manager.block'), + $container->get('plugin.manager.condition'), + $container->get('plugin.manager.display_variant'), + $container->get('context.handler'), + $container->get('user.shared_tempstore') + ); + } + + /** + * Presents a list of blocks to add to the variant. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The current request. + * @param string $block_display + * The identifier of the block display variant. + * @param string $tempstore_id + * The identifier of the temporary store. + * + * @return array + * The block selection page. + */ + public function selectBlock(Request $request, $block_display, $tempstore_id) { + $cached_values = $this->tempstore->get($tempstore_id)->get($block_display); + /** @var \Drupal\page_manager\Plugin\DisplayVariant\PageBlockDisplayVariant $variant_plugin */ + $variant_plugin = $cached_values['plugin']; + + // Rehydrate the contexts on this end. + $contexts = []; + /** + * @var string $context_name + * @var \Drupal\Core\Plugin\Context\ContextDefinitionInterface $context_definition + */ + foreach ($cached_values['contexts'] as $context_name => $context_definition) { + $contexts[$context_name] = new Context($context_definition); + } + $variant_plugin->setContexts($contexts); + + // Add a section containing the available blocks to be added to the variant. + $build = [ + '#type' => 'container', + '#attached' => [ + 'library' => [ + 'core/drupal.ajax', + ], + ], + ]; + $available_plugins = $this->blockManager->getDefinitionsForContexts($variant_plugin->getContexts()); + // Order by category, and then by admin label. + $available_plugins = $this->blockManager->getSortedDefinitions($available_plugins); + foreach ($available_plugins as $plugin_id => $plugin_definition) { + // Make a section for each region. + $category = $plugin_definition['category']; + $category_key = 'category-' . $category; + if (!isset($build[$category_key])) { + $build[$category_key] = [ + '#type' => 'fieldgroup', + '#title' => $category, + 'content' => [ + '#theme' => 'links', + ], + ]; + } + // Add a link for each available block within each region. + $build[$category_key]['content']['#links'][$plugin_id] = [ + 'title' => $plugin_definition['admin_label'], + 'url' => Url::fromRoute('panelizer.wizard.step.content.add_block', [ + 'block_display' => $block_display, + 'block_id' => $plugin_id, + 'region' => $request->query->get('region'), + 'destination' => $request->query->get('destination'), + ]), + 'attributes' => $this->getAjaxAttributes(), + ]; + } + return $build; + } + +} diff --git a/src/Form/PanelizerWizardAddBlockForm.php b/src/Form/PanelizerWizardAddBlockForm.php new file mode 100644 index 0000000..1d173c7 --- /dev/null +++ b/src/Form/PanelizerWizardAddBlockForm.php @@ -0,0 +1,82 @@ +blockManager = $block_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('user.shared_tempstore'), + $container->get('plugin.manager.block') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'panelizer_wizard_add_block_form'; + } + + /** + * {@inheritdoc} + */ + protected function prepareBlock($plugin_id) { + $block = $this->blockManager->createInstance($plugin_id); + $block_id = $this->getVariantPlugin()->addBlock($block->getConfiguration()); + return $this->getVariantPlugin()->getBlock($block_id); + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state, Request $request = NULL, $block_display = NULL, $block_id = NULL) { + $form = parent::buildForm($form, $form_state, $block_display, $block_id); + $form['region']['#default_value'] = $request->query->get('region'); + return $form; + } + + /** + * {@inheritdoc} + */ + protected function submitText() { + return $this->t('Add block'); + } + +} diff --git a/src/Form/PanelizerWizardConfigureBlockFormBase.php b/src/Form/PanelizerWizardConfigureBlockFormBase.php new file mode 100644 index 0000000..d442127 --- /dev/null +++ b/src/Form/PanelizerWizardConfigureBlockFormBase.php @@ -0,0 +1,199 @@ +tempstore = $tempstore; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('user.shared_tempstore') + ); + } + + /** + * Get the tempstore id. + * + * @return string + */ + protected function getTempstoreId() { + return 'panelizer.block_display'; + } + + /** + * Get the tempstore. + * + * @return \Drupal\user\SharedTempStore + */ + protected function getTempstore() { + return $this->tempstore->get($this->getTempstoreId()); + } + + /** + * Prepares the block plugin based on the block ID. + * + * @param string $block_id + * Either a block ID, or the plugin ID used to create a new block. + * + * @return \Drupal\Core\Block\BlockPluginInterface + * The block plugin. + */ + abstract protected function prepareBlock($block_id); + + /** + * Returns the text to use for the submit button. + * + * @return string + * The submit button text. + */ + abstract protected function submitText(); + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state, $block_display = NULL, $block_id = NULL) { + $cached_values = $this->tempstore->get($this->getTempstoreId())->get($block_display); + /** @var \Drupal\page_manager\Plugin\DisplayVariant\PageBlockDisplayVariant $variant_plugin */ + $this->variantPlugin = $cached_values['plugin']; + + // Rehydrate the contexts on this end. + $contexts = []; + /** + * @var string $context_name + * @var \Drupal\Core\Plugin\Context\ContextDefinitionInterface $context_definition + */ + foreach ($cached_values['contexts'] as $context_name => $context_definition) { + $contexts[$context_name] = new Context($context_definition); + } + $this->variantPlugin->setContexts($contexts); + + $this->block = $this->prepareBlock($block_id); + $form_state->set('variant_id', $this->getVariantPlugin()->id()); + $form_state->set('block_id', $this->block->getConfiguration()['uuid']); + + $form['#tree'] = TRUE; + $form['settings'] = $this->block->buildConfigurationForm([], $form_state); + $form['settings']['id'] = [ + '#type' => 'value', + '#value' => $this->block->getPluginId(), + ]; + $form['region'] = [ + '#title' => $this->t('Region'), + '#type' => 'select', + '#options' => $this->getVariantPlugin()->getRegionNames(), + '#default_value' => $this->getVariantPlugin()->getRegionAssignment($this->block->getConfiguration()['uuid']), + '#required' => TRUE, + ]; + + if ($this->block instanceof ContextAwarePluginInterface) { + $form['context_mapping'] = $this->addContextAssignmentElement($this->block, $this->getVariantPlugin()->getContexts()); + } + + $form['actions']['submit'] = [ + '#type' => 'submit', + '#value' => $this->submitText(), + '#button_type' => 'primary', + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + // The page might have been serialized, resulting in a new variant + // collection. Refresh the block object. + $this->block = $this->getVariantPlugin()->getBlock($form_state->get('block_id')); + + $settings = (new FormState())->setValues($form_state->getValue('settings')); + // Call the plugin validate handler. + $this->block->validateConfigurationForm($form, $settings); + // Update the original form values. + $form_state->setValue('settings', $settings->getValues()); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $settings = (new FormState())->setValues($form_state->getValue('settings')); + + // Call the plugin submit handler. + $this->block->submitConfigurationForm($form, $settings); + // Update the original form values. + $form_state->setValue('settings', $settings->getValues()); + + if ($this->block instanceof ContextAwarePluginInterface) { + $this->block->setContextMapping($form_state->getValue('context_mapping', [])); + } + + $this->getVariantPlugin()->updateBlock($this->block->getConfiguration()['uuid'], ['region' => $form_state->getValue('region')]); + + $cached_values = $this->getTempstore()->get($form_state->get('variant_id')); + $cached_values['plugin'] = $this->getVariantPlugin(); + $this->getTempstore()->set($form_state->get('variant_id'), $cached_values); + } + + /** + * Gets the variant plugin for this page variant entity. + * + * @return \Drupal\ctools\Plugin\BlockVariantInterface + */ + protected function getVariantPlugin() { + return $this->variantPlugin; + } + +} diff --git a/src/Form/PanelizerWizardContentForm.php b/src/Form/PanelizerWizardContentForm.php new file mode 100644 index 0000000..e16c553 --- /dev/null +++ b/src/Form/PanelizerWizardContentForm.php @@ -0,0 +1,257 @@ +tempstore = $tempstore; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('user.shared_tempstore') + ); + } + + /** + * Get the tempstore ID. + * + * @return string + */ + protected function getTempstoreId() { + return 'panelizer.block_display'; + } + + /** + * Get the tempstore. + * + * @return \Drupal\user\SharedTempStore + */ + protected function getTempstore() { + return $this->tempstore->get($this->getTempstoreId()); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'panelizer_wizard_block_page_content'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $cached_values = $form_state->getTemporaryValue('wizard'); + /** @var \Drupal\page_manager\Plugin\DisplayVariant\PageBlockDisplayVariant $variant_plugin */ + $variant_plugin = $cached_values['plugin']; + + // Store the block display plugin so we can get it in our dialogs. + if (!empty($this->getTempstore()->get($variant_plugin->id())['plugin'])) { + $variant_plugin->setConfiguration($this->getTempstore()->get($variant_plugin->id())['plugin']->getConfiguration()); + $form_state->setTemporaryValue('wizard', $cached_values); + } + $context_definitions = []; + foreach ($variant_plugin->getContexts() as $context_name => $context) { + $context_definitions[$context_name] = $context->getContextDefinition(); + } + $this->getTempstore()->set($variant_plugin->id(), [ + 'plugin' => $variant_plugin, + 'access' => $cached_values['access'], + 'contexts' => $context_definitions, + ]); + + // Set up the attributes used by a modal to prevent duplication later. + $attributes = $this->getAjaxAttributes(); + $add_button_attributes = $this->getAjaxButtonAttributes(); + + if ($block_assignments = $variant_plugin->getRegionAssignments()) { + // Build a table of all blocks used by this variant. + $form['add'] = [ + '#type' => 'link', + '#title' => $this->t('Add new block'), + '#url' => Url::fromRoute('panelizer.wizard.step.content.select_block', [ + 'block_display' => $variant_plugin->id(), + 'destination' => $this->getRequest()->getRequestUri(), + ]), + '#attributes' => $add_button_attributes, + '#attached' => [ + 'library' => [ + 'core/drupal.ajax', + ], + ], + ]; + $form['blocks'] = [ + '#type' => 'table', + '#header' => [ + $this->t('Label'), + $this->t('Plugin ID'), + $this->t('Region'), + $this->t('Weight'), + $this->t('Operations'), + ], + '#empty' => $this->t('There are no regions for blocks.'), + ]; + // Loop through the blocks per region. + foreach ($block_assignments as $region => $blocks) { + // Add a section for each region and allow blocks to be dragged between + // them. + $form['blocks']['#tabledrag'][] = [ + 'action' => 'match', + 'relationship' => 'sibling', + 'group' => 'block-region-select', + 'subgroup' => 'block-region-' . $region, + 'hidden' => FALSE, + ]; + $form['blocks']['#tabledrag'][] = [ + 'action' => 'order', + 'relationship' => 'sibling', + 'group' => 'block-weight', + 'subgroup' => 'block-weight-' . $region, + ]; + $form['blocks'][$region] = [ + '#attributes' => [ + 'class' => ['region-title', 'region-title-' . $region], + 'no_striping' => TRUE, + ], + ]; + $form['blocks'][$region]['title'] = [ + '#markup' => $variant_plugin->getRegionName($region), + '#wrapper_attributes' => [ + 'colspan' => 5, + ], + ]; + $form['blocks'][$region . '-message'] = [ + '#attributes' => [ + 'class' => [ + 'region-message', + 'region-' . $region . '-message', + empty($blocks) ? 'region-empty' : 'region-populated', + ], + ], + ]; + $form['blocks'][$region . '-message']['message'] = [ + '#markup' => '' . $this->t('No blocks in this region') . '', + '#wrapper_attributes' => [ + 'colspan' => 5, + ], + ]; + + /** @var \Drupal\Core\Block\BlockPluginInterface[] $blocks */ + foreach ($blocks as $block_id => $block) { + $row = [ + '#attributes' => [ + 'class' => ['draggable'], + ], + ]; + $row['label']['#markup'] = $block->label(); + $row['id']['#markup'] = $block->getPluginId(); + // Allow the region to be changed for each block. + $row['region'] = [ + '#title' => $this->t('Region'), + '#title_display' => 'invisible', + '#type' => 'select', + '#options' => $variant_plugin->getRegionNames(), + '#default_value' => $variant_plugin->getRegionAssignment($block_id), + '#attributes' => [ + 'class' => ['block-region-select', 'block-region-' . $region], + ], + ]; + // Allow the weight to be changed for each block. + $configuration = $block->getConfiguration(); + $row['weight'] = [ + '#type' => 'weight', + '#default_value' => isset($configuration['weight']) ? $configuration['weight'] : 0, + '#title' => $this->t('Weight for @block block', ['@block' => $block->label()]), + '#title_display' => 'invisible', + '#attributes' => [ + 'class' => ['block-weight', 'block-weight-' . $region], + ], + ]; + // Add the operation links. + $operations = []; + $operations['edit'] = [ + 'title' => $this->t('Edit'), + 'url' => Url::fromRoute('panelizer.wizard.step.content.edit_block', [ + 'block_display' => $variant_plugin->id(), + 'block_id' => $block_id, + 'destination' => $this->getRequest()->getRequestUri(), + ]), + 'attributes' => $attributes, + ]; + $operations['delete'] = [ + 'title' => $this->t('Delete'), + 'url' => Url::fromRoute('panelizer.wizard.step.content.delete_block', [ + 'block_display' => $variant_plugin->id(), + 'block_id' => $block_id, + 'destination' => $this->getRequest()->getRequestUri(), + ]), + 'attributes' => $attributes, + ]; + + $row['operations'] = [ + '#type' => 'operations', + '#links' => $operations, + ]; + $form['blocks'][$block_id] = $row; + } + } + } + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $cached_values = $form_state->getTemporaryValue('wizard'); + /** @var \Drupal\page_manager\Plugin\DisplayVariant\PageBlockDisplayVariant $variant_plugin */ + $variant_plugin = $cached_values['plugin']; + + // If the blocks were rearranged, update their values. + if (!$form_state->isValueEmpty('blocks')) { + foreach ($form_state->getValue('blocks') as $block_id => $block_values) { + $variant_plugin->updateBlock($block_id, $block_values); + } + } + + // Remove from the tempstore so we refresh from the database the next time + // we come here. + $this->getTempstore()->delete($variant_plugin->id()); + } + +} diff --git a/src/Form/PanelizerWizardDeleteBlockForm.php b/src/Form/PanelizerWizardDeleteBlockForm.php new file mode 100644 index 0000000..e5bf70d --- /dev/null +++ b/src/Form/PanelizerWizardDeleteBlockForm.php @@ -0,0 +1,101 @@ +get($this->getTempstoreId()); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'panelizer_wizard_delete_block_form'; + } + + /** + * {@inheritdoc} + */ + public function getQuestion() { + return $this->t('Are you sure you want to delete the block %label?', ['%label' => $this->block->label()]); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + return \Drupal::request()->attributes->get('destination'); + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return $this->t('Delete'); + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state, $block_display = NULL, $block_id = NULL) { + $this->plugin = $this->getTempstore()->get($block_display)['plugin']; + $this->block = $this->plugin->getBlock($block_id); + $form['block_display'] = [ + '#type' => 'value', + '#value' => $block_display + ]; + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $this->plugin->removeBlock($this->block->getConfiguration()['uuid']); + $cached_values = $this->getTempstore()->get($form_state->getValue('block_display')); + $cached_values['plugin'] = $this->plugin; + $this->getTempstore()->set($form_state->getValue('block_display'), $cached_values); + drupal_set_message($this->t('The block %label has been removed.', ['%label' => $this->block->label()])); + } + +} diff --git a/src/Form/PanelizerWizardEditBlockForm.php b/src/Form/PanelizerWizardEditBlockForm.php new file mode 100644 index 0000000..ddf9366 --- /dev/null +++ b/src/Form/PanelizerWizardEditBlockForm.php @@ -0,0 +1,36 @@ +getVariantPlugin()->getBlock($block_id); + } + + /** + * {@inheritdoc} + */ + protected function submitText() { + return $this->t('Update block'); + } + +} diff --git a/src/Wizard/PanelizerWizard.php b/src/Wizard/PanelizerWizard.php index c24e3bc..fc7ace2 100644 --- a/src/Wizard/PanelizerWizard.php +++ b/src/Wizard/PanelizerWizard.php @@ -10,6 +10,7 @@ namespace Drupal\panelizer\Wizard; use Drupal\Core\Form\FormStateInterface; use Drupal\ctools\Wizard\FormWizardBase; +use Drupal\panelizer\Form\PanelizerWizardContentForm; class PanelizerWizard extends FormWizardBase { @@ -40,19 +41,23 @@ class PanelizerWizard extends FormWizardBase { 'form' => 'Drupal\panelizer\Form\PanelizerWizardContextForm', 'title' => $this->t('Contexts'), ], - 'layout' => [ - 'form' => 'Drupal\panelizer\Form\PanelizerWizardLayoutForm', - 'title' => $this->t('Layout'), - ], ]; // Add any wizard operations from the plugin itself. - $variant_plugin = $this->getPanelsVariant($cached_values); - $variant_plugin->setContexts($this->getContexts($cached_values)); - foreach ($variant_plugin->getWizardOperations($cached_values) as $name => $operation) { - $operation['values']['plugin'] = $variant_plugin; - $operation['submit'][] = '::submitVariantStep'; - $operations[$name] = $operation; + if (isset($cached_values['page_variant'])) { + $page_variant = $cached_values['page_variant']; + $variant_plugin = $page_variant->getVariantPlugin(); + $variant_plugin->setContexts($this->getContexts($cached_values)); + foreach ($variant_plugin->getWizardOperations($cached_values) as $name => $operation) { + $operation['values']['plugin'] = $variant_plugin; + $operation['submit'][] = '::submitVariantStep'; + $operations[$name] = $operation; + } + + // Change the class that manages the Content step. + if (isset($operations['content'])) { + $operations['content']['form'] = PanelizerWizardContentForm::class; + } } return $operations; @@ -74,6 +79,17 @@ class PanelizerWizard extends FormWizardBase { $cached_values['id'] = $this->getMachineName(); // Some variants like PanelsDisplayVariant need this. Set it to empty. $cached_values['access'] = []; + if (empty($cached_values['page_variant'])) { + $variant_plugin_id = 'panels_variant'; + $panels_variant = \Drupal::entityManager() + ->getStorage('page_variant') + ->create([ + 'variant' => $variant_plugin_id, + //'page' => $page->id(), + 'id' => $cached_values['id'] . '__variant', + ]); + $cached_values['page_variant'] = $panels_variant; + } $form_state->setTemporaryValue('wizard', $cached_values); return $form; } @@ -110,25 +126,17 @@ class PanelizerWizard extends FormWizardBase { * Submission callback for the variant plugin steps. */ public function submitVariantStep(array &$form, FormStateInterface $form_state) { - // Do we need to do anything here? - } - - /** - * Returns the Panels variant. - */ - function getPanelsVariant(&$cached_values) { - if (empty($cached_values['plugin'])) { - $variant_plugin_id = 'panels_variant'; - $panels_variant = \Drupal::entityManager() - ->getStorage('page_variant') - ->create([ - 'variant' => $variant_plugin_id, - //'page' => $page->id(), - 'id' => $cached_values['id'] . '__variant', - ]); - $cached_values['plugin'] = $panels_variant; + $cached_values = $form_state->getTemporaryValue('wizard'); + /** @var \Drupal\page_manager\PageVariantInterface $page_variant */ + $page_variant = $cached_values['page_variant']; + /** @var \Drupal\Core\Display\VariantInterface $plugin */ + $plugin = $cached_values['plugin']; + + // Make sure the variant plugin on the page variant gets the configuration + // from the 'plugin' which should have been setup by the variant's steps. + if (!empty($plugin) && !empty($page_variant)) { + $page_variant->getVariantPlugin()->setConfiguration($plugin->getConfiguration()); } - return $cached_values['plugin']; } }