diff --git a/core/modules/settings_tray/settings_tray.links.contextual.yml b/core/modules/settings_tray/settings_tray.links.contextual.yml index 5534ab2..9b3e013 100644 --- a/core/modules/settings_tray/settings_tray.links.contextual.yml +++ b/core/modules/settings_tray/settings_tray.links.contextual.yml @@ -1,6 +1,6 @@ settings_tray.block_configure: title: 'Quick edit' - route_name: 'entity.block.off_canvas_form' + route_name: 'entity.block.settings_tray_form group: 'block' options: attributes: diff --git a/core/modules/settings_tray/settings_tray.module b/core/modules/settings_tray/settings_tray.module index 208e1ef..7aa4088 100644 --- a/core/modules/settings_tray/settings_tray.module +++ b/core/modules/settings_tray/settings_tray.module @@ -8,7 +8,7 @@ use Drupal\Core\Asset\AttachedAssetsInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Url; -use Drupal\settings_tray\Block\BlockEntityOffCanvasForm; +use Drupal\settings_tray\Block\BlockEntitySettingTrayForm; use Drupal\settings_tray\Form\SystemBrandingOffCanvasForm; use Drupal\settings_tray\Form\SystemMenuOffCanvasForm; use Drupal\block\entity\Block; @@ -21,7 +21,11 @@ function settings_tray_help($route_name, RouteMatchInterface $route_match) { switch ($route_name) { case 'help.page.settings_tray': $output = '

' . t('About') . '

'; - $output .= '

' . t('The Settings Tray module allows users with the Administer blocks and Use contextual links permissions to edit blocks without visiting a separate page. For more information, see the online documentation for the Settings Tray module.', [':handbook_url' => 'https://www.drupal.org/documentation/modules/settings_tray', ':administer_block_permission' => \Drupal::url('user.admin_permissions', [], ['fragment' => 'module-block']), ':contextual_permission' => \Drupal::url('user.admin_permissions', [], ['fragment' => 'module-contextual'])]) . '

'; + $output .= '

' . t('The Settings Tray module allows users with the Administer blocks and Use contextual links permissions to edit blocks without visiting a separate page. For more information, see the online documentation for the Settings Tray module.', [ + ':handbook_url' => 'https://www.drupal.org/documentation/modules/settings_tray', + ':administer_block_permission' => \Drupal::url('user.admin_permissions', [], ['fragment' => 'module-block']), + ':contextual_permission' => \Drupal::url('user.admin_permissions', [], ['fragment' => 'module-contextual']), + ]) . '

'; $output .= '

' . t('Uses') . '

'; $output .= '
'; $output .= '
' . t('Editing blocks in place') . '
'; @@ -70,7 +74,8 @@ function settings_tray_contextual_links_view_alter(&$element, $items) { function _settings_tray_has_block_overrides(BlockInterface $block) { // @todo Replace the following with $block->hasOverrides() in https://www.drupal.org/project/drupal/issues/2910353 // and remove this function. - return \Drupal::config($block->getEntityType()->getConfigPrefix() . '.' . $block->id())->hasOverrides(); + return \Drupal::config($block->getEntityType() + ->getConfigPrefix() . '.' . $block->id())->hasOverrides(); } /** @@ -78,8 +83,8 @@ function _settings_tray_has_block_overrides(BlockInterface $block) { */ function settings_tray_block_view_alter(array &$build) { if (isset($build['#contextual_links']['block'])) { - // Ensure that contextual links vary by whether the block has config overrides - // or not. + // Ensure that contextual links vary by whether the block has config + // overrides or not. // @see _contextual_links_to_id() $build['#contextual_links']['block']['metadata']['has_overrides'] = _settings_tray_has_block_overrides($build['#block']) ? 1 : 0; } @@ -98,8 +103,8 @@ function settings_tray_block_view_alter(array &$build) { function settings_tray_entity_type_build(array &$entity_types) { /* @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */ $entity_types['block'] - ->setFormClass('off_canvas', BlockEntityOffCanvasForm::class) - ->setLinkTemplate('off_canvas-form', '/admin/structure/block/manage/{block}/off-canvas'); + ->setFormClass('settings_tray', BlockEntitySettingTrayForm::class) + ->setLinkTemplate('settings_tray-form', '/admin/structure/block/manage/{block}/settings-tray'); } /** @@ -121,7 +126,8 @@ function settings_tray_preprocess_block(&$variables) { $block_plugin = $block_plugin_manager->createInstance($variables['plugin_id']); if (isset($variables['elements']['#contextual_links']['block']['route_parameters']['block'])) { $block = Block::load($variables['elements']['#contextual_links']['block']['route_parameters']['block']); - if ($access_checker->accessBlockPlugin($block_plugin)->isAllowed() && !_settings_tray_has_block_overrides($block)) { + if ($access_checker->accessBlockPlugin($block_plugin) + ->isAllowed() && !_settings_tray_has_block_overrides($block)) { // Add class and attributes to all blocks to allow Javascript to target. $variables['attributes']['class'][] = 'settings-tray-editable'; $variables['attributes']['data-drupal-settingstray'] = 'editable'; @@ -142,7 +148,8 @@ function settings_tray_preprocess_block(&$variables) { */ function settings_tray_toolbar_alter(&$items) { $items['contextual']['#cache']['contexts'][] = 'user.permissions'; - if (isset($items['contextual']['tab']) && \Drupal::currentUser()->hasPermission('administer blocks')) { + if (isset($items['contextual']['tab']) && \Drupal::currentUser() + ->hasPermission('administer blocks')) { $items['contextual']['#weight'] = -1000; $items['contextual']['#attached']['library'][] = 'settings_tray/drupal.settings_tray'; $items['contextual']['tab']['#attributes']['data-drupal-settingstray'] = 'toggle'; diff --git a/core/modules/settings_tray/settings_tray.routing.yml b/core/modules/settings_tray/settings_tray.routing.yml index f8e2bfe..370fc7f 100644 --- a/core/modules/settings_tray/settings_tray.routing.yml +++ b/core/modules/settings_tray/settings_tray.routing.yml @@ -1,9 +1,16 @@ -entity.block.off_canvas_form: - path: '/admin/structure/block/manage/{block}/off-canvas' +entity.block.settings_tray_form: + path: '/admin/structure/block/manage/{block}/settings-tray' defaults: - _entity_form: 'block.off_canvas' - _title_callback: '\Drupal\settings_tray\Block\BlockEntityOffCanvasForm::title' + _entity_form: 'block.settings_tray' + _title_callback: '\Drupal\settings_tray\Block\BlockEntitySettingTrayForm::title' requirements: _permission: 'administer blocks' _access_block_plugin_has_settings_tray_form: 'TRUE' _access_block_has_overrides_settings_tray_form: 'TRUE' + +# Deprecated. +# @see entity.block.settings_tray_form +# @see \Drupal\settings_tray\RouteProcessor\BlockEntityOffCanvasFormRouteProcessorBC +# @todo Remove in Drupal 9.0.0. +entity.block.off_canvas_form: + path: '' diff --git a/core/modules/settings_tray/settings_tray.services.yml b/core/modules/settings_tray/settings_tray.services.yml index 7c57e95..9f61546 100644 --- a/core/modules/settings_tray/settings_tray.services.yml +++ b/core/modules/settings_tray/settings_tray.services.yml @@ -7,3 +7,12 @@ services: class: Drupal\settings_tray\Access\BlockPluginHasSettingsTrayFormAccessCheck tags: - { name: access_check, applies_to: _access_block_plugin_has_settings_tray_form } + + # BC layers. + # @todo Remove in Drupal 9.0.0. + settings_tray.route_processor_off_canvas_form_bc: + class: \Drupal\settings_tray\RouteProcessor\BlockEntityOffCanvasFormRouteProcessorBC + arguments: ['@router.route_provider'] + public: false + tags: + - { name: route_processor_outbound } diff --git a/core/modules/settings_tray/src/Block/BlockEntityOffCanvasForm.php b/core/modules/settings_tray/src/Block/BlockEntityOffCanvasForm.php deleted file mode 100644 index 2c6f80d..0000000 --- a/core/modules/settings_tray/src/Block/BlockEntityOffCanvasForm.php +++ /dev/null @@ -1,194 +0,0 @@ - once - // https://www.drupal.org/node/2359901 is fixed. - return $this->t('Configure @block', ['@block' => $block->getPlugin()->getPluginDefinition()['admin_label']]); - } - - /** - * {@inheritdoc} - */ - public function form(array $form, FormStateInterface $form_state) { - $form = parent::form($form, $form_state); - - // Create link to full block form. - $query = []; - if ($destination = $this->getRequest()->query->get('destination')) { - $query['destination'] = $destination; - } - $form['advanced_link'] = [ - '#type' => 'link', - '#title' => $this->t('Advanced block options'), - '#url' => $this->entity->toUrl('edit-form', ['query' => $query]), - '#weight' => 1000, - ]; - - // Remove the ID and region elements. - unset($form['id'], $form['region'], $form['settings']['admin_label']); - - if (isset($form['settings']['label_display']) && isset($form['settings']['label'])) { - // Only show the label input if the label will be shown on the page. - $form['settings']['label_display']['#weight'] = -100; - $form['settings']['label']['#states']['visible'] = [ - ':input[name="settings[label_display]"]' => ['checked' => TRUE], - ]; - - // Relabel to "Block title" because on the front-end this may be confused - // with page title. - $form['settings']['label']['#title'] = $this->t("Block title"); - $form['settings']['label_display']['#title'] = $this->t("Display block title"); - } - return $form; - } - - /** - * {@inheritdoc} - */ - protected function actions(array $form, FormStateInterface $form_state) { - $actions = parent::actions($form, $form_state); - $actions['submit']['#value'] = $this->t('Save @block', ['@block' => $this->entity->getPlugin()->getPluginDefinition()['admin_label']]); - $actions['delete']['#access'] = FALSE; - return $actions; - } - - /** - * {@inheritdoc} - */ - protected function buildVisibilityInterface(array $form, FormStateInterface $form_state) { - // Do not display the visibility. - return []; - } - - /** - * {@inheritdoc} - */ - protected function validateVisibility(array $form, FormStateInterface $form_state) { - // Intentionally empty. - } - - /** - * {@inheritdoc} - */ - protected function submitVisibility(array $form, FormStateInterface $form_state) { - // Intentionally empty. - } - - /** - * {@inheritdoc} - */ - protected function getPluginForm(BlockPluginInterface $block) { - if ($block instanceof PluginWithFormsInterface) { - return $this->pluginFormFactory->createInstance($block, 'settings_tray', 'configure'); - } - return $block; - } - - /** - * {@inheritdoc} - */ - public function buildForm(array $form, FormStateInterface $form_state) { - $form = parent::buildForm($form, $form_state); - $form['actions']['submit']['#ajax'] = [ - 'callback' => '::submitFormDialog', - ]; - $form['#attached']['library'][] = 'core/drupal.dialog.ajax'; - - // static::submitFormDialog() requires data-drupal-selector to be the same - // between the various Ajax requests. A bug in - // \Drupal\Core\Form\FormBuilder prevents that from happening unless - // $form['#id'] is also the same. Normally, #id is set to a unique HTML ID - // via Html::getUniqueId(), but here we bypass that in order to work around - // the data-drupal-selector bug. This is okay so long as we assume that this - // form only ever occurs once on a page. - // @todo Remove this workaround once https://www.drupal.org/node/2897377 is - // fixed. - $form['#id'] = Html::getId($form_state->getBuildInfo()['form_id']); - - return $form; - } - - /** - * Submit form dialog #ajax callback. - * - * @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. - * - * @return \Drupal\Core\Ajax\AjaxResponse - * An AJAX response that display validation error messages or redirects - * to a URL - * - * @todo Repalce this callback with generic trait in - * https://www.drupal.org/node/2896535. - */ - public function submitFormDialog(array &$form, FormStateInterface $form_state) { - $response = new AjaxResponse(); - if ($form_state->hasAnyErrors()) { - $form['status_messages'] = [ - '#type' => 'status_messages', - '#weight' => -1000, - ]; - $command = new ReplaceCommand('[data-drupal-selector="' . $form['#attributes']['data-drupal-selector'] . '"]', $form); - } - else { - if ($redirect_url = $this->getRedirectUrl()) { - $command = new RedirectCommand($redirect_url->setAbsolute()->toString()); - } - else { - // Settings Tray always provides a destination. - throw new \Exception("No destination provided by Settings Tray form"); - } - } - return $response->addCommand($command); - } - - /** - * Gets the form's redirect URL from 'destination' provide in the request. - * - * @return \Drupal\Core\Url|null - * The redirect URL or NULL if dialog should just be closed. - */ - protected function getRedirectUrl() { - // \Drupal\Core\Routing\RedirectDestination::get() cannot be used directly - // because it will use if 'destination' is not in the query - // string. - if ($this->getRequest()->query->has('destination') && $destination = $this->getRedirectDestination()->get()) { - return Url::fromUserInput('/' . $destination); - } - } - -} diff --git a/core/modules/settings_tray/src/Block/BlockEntitySettingTrayForm.php b/core/modules/settings_tray/src/Block/BlockEntitySettingTrayForm.php new file mode 100644 index 0000000..fd44b0c --- /dev/null +++ b/core/modules/settings_tray/src/Block/BlockEntitySettingTrayForm.php @@ -0,0 +1,194 @@ + once + // https://www.drupal.org/node/2359901 is fixed. + return $this->t('Configure @block', ['@block' => $block->getPlugin()->getPluginDefinition()['admin_label']]); + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + + // Create link to full block form. + $query = []; + if ($destination = $this->getRequest()->query->get('destination')) { + $query['destination'] = $destination; + } + $form['advanced_link'] = [ + '#type' => 'link', + '#title' => $this->t('Advanced block options'), + '#url' => $this->entity->toUrl('edit-form', ['query' => $query]), + '#weight' => 1000, + ]; + + // Remove the ID and region elements. + unset($form['id'], $form['region'], $form['settings']['admin_label']); + + if (isset($form['settings']['label_display']) && isset($form['settings']['label'])) { + // Only show the label input if the label will be shown on the page. + $form['settings']['label_display']['#weight'] = -100; + $form['settings']['label']['#states']['visible'] = [ + ':input[name="settings[label_display]"]' => ['checked' => TRUE], + ]; + + // Relabel to "Block title" because on the front-end this may be confused + // with page title. + $form['settings']['label']['#title'] = $this->t("Block title"); + $form['settings']['label_display']['#title'] = $this->t("Display block title"); + } + return $form; + } + + /** + * {@inheritdoc} + */ + protected function actions(array $form, FormStateInterface $form_state) { + $actions = parent::actions($form, $form_state); + $actions['submit']['#value'] = $this->t('Save @block', ['@block' => $this->entity->getPlugin()->getPluginDefinition()['admin_label']]); + $actions['delete']['#access'] = FALSE; + return $actions; + } + + /** + * {@inheritdoc} + */ + protected function buildVisibilityInterface(array $form, FormStateInterface $form_state) { + // Do not display the visibility. + return []; + } + + /** + * {@inheritdoc} + */ + protected function validateVisibility(array $form, FormStateInterface $form_state) { + // Intentionally empty. + } + + /** + * {@inheritdoc} + */ + protected function submitVisibility(array $form, FormStateInterface $form_state) { + // Intentionally empty. + } + + /** + * {@inheritdoc} + */ + protected function getPluginForm(BlockPluginInterface $block) { + if ($block instanceof PluginWithFormsInterface) { + return $this->pluginFormFactory->createInstance($block, 'settings_tray', 'configure'); + } + return $block; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $form = parent::buildForm($form, $form_state); + $form['actions']['submit']['#ajax'] = [ + 'callback' => '::submitFormDialog', + ]; + $form['#attached']['library'][] = 'core/drupal.dialog.ajax'; + + // static::submitFormDialog() requires data-drupal-selector to be the same + // between the various Ajax requests. A bug in + // \Drupal\Core\Form\FormBuilder prevents that from happening unless + // $form['#id'] is also the same. Normally, #id is set to a unique HTML ID + // via Html::getUniqueId(), but here we bypass that in order to work around + // the data-drupal-selector bug. This is okay so long as we assume that this + // form only ever occurs once on a page. + // @todo Remove this workaround once https://www.drupal.org/node/2897377 is + // fixed. + $form['#id'] = Html::getId($form_state->getBuildInfo()['form_id']); + + return $form; + } + + /** + * Submit form dialog #ajax callback. + * + * @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. + * + * @return \Drupal\Core\Ajax\AjaxResponse + * An AJAX response that display validation error messages or redirects + * to a URL + * + * @todo Repalce this callback with generic trait in + * https://www.drupal.org/node/2896535. + */ + public function submitFormDialog(array &$form, FormStateInterface $form_state) { + $response = new AjaxResponse(); + if ($form_state->hasAnyErrors()) { + $form['status_messages'] = [ + '#type' => 'status_messages', + '#weight' => -1000, + ]; + $command = new ReplaceCommand('[data-drupal-selector="' . $form['#attributes']['data-drupal-selector'] . '"]', $form); + } + else { + if ($redirect_url = $this->getRedirectUrl()) { + $command = new RedirectCommand($redirect_url->setAbsolute()->toString()); + } + else { + // Settings Tray always provides a destination. + throw new \Exception("No destination provided by Settings Tray form"); + } + } + return $response->addCommand($command); + } + + /** + * Gets the form's redirect URL from 'destination' provide in the request. + * + * @return \Drupal\Core\Url|null + * The redirect URL or NULL if dialog should just be closed. + */ + protected function getRedirectUrl() { + // \Drupal\Core\Routing\RedirectDestination::get() cannot be used directly + // because it will use if 'destination' is not in the query + // string. + if ($this->getRequest()->query->has('destination') && $destination = $this->getRedirectDestination()->get()) { + return Url::fromUserInput('/' . $destination); + } + } + +} diff --git a/core/modules/settings_tray/src/RouteProcessor/BlockEntityOffCanvasFormRouteProcessorBC.php b/core/modules/settings_tray/src/RouteProcessor/BlockEntityOffCanvasFormRouteProcessorBC.php new file mode 100644 index 0000000..48935ae --- /dev/null +++ b/core/modules/settings_tray/src/RouteProcessor/BlockEntityOffCanvasFormRouteProcessorBC.php @@ -0,0 +1,65 @@ +routeProvider = $route_provider; + } + + /** + * {@inheritdoc} + */ + public function processOutbound($route_name, Route $route, array &$parameters, BubbleableMetadata $bubbleable_metadata = NULL) { + if ($route_name === 'entity.block.off_canvas_form') { + $redirected_route_name = 'entity.block.settings_tray_form'; + @trigger_error(sprintf("The '%s' route is deprecated since version 8.5.x and will be removed in 9.0.0. Use the '%s' route instead.", $route_name, $redirected_route_name), E_USER_DEPRECATED); + static::overwriteRoute($route, $this->routeProvider->getRouteByName($redirected_route_name)); + } + } + + /** + * Overwrites one route's metadata with the other's. + * + * @param \Symfony\Component\Routing\Route $target_route + * The route whose metadata to overwrite. + * @param \Symfony\Component\Routing\Route $source_route + * The route whose metadata to read from. + * + * @see \Symfony\Component\Routing\Route + */ + protected static function overwriteRoute(Route $target_route, Route $source_route) { + $target_route->setPath($source_route->getPath()); + $target_route->setDefaults($source_route->getDefaults()); + $target_route->setRequirements($source_route->getRequirements()); + $target_route->setOptions($source_route->getOptions()); + $target_route->setHost($source_route->getHost()); + $target_route->setSchemes($source_route->getSchemes()); + $target_route->setMethods($source_route->getMethods()); + } + +} diff --git a/core/modules/settings_tray/tests/src/Functional/BcRoutesTest.php b/core/modules/settings_tray/tests/src/Functional/BcRoutesTest.php new file mode 100644 index 0000000..869dea2 --- /dev/null +++ b/core/modules/settings_tray/tests/src/Functional/BcRoutesTest.php @@ -0,0 +1,35 @@ +placeBlock('system_powered_by_block'); + $url_for_current_route = Url::fromRoute('entity.block.settings_tray_form', ['block' => $block->id()])->toString(TRUE)->getGeneratedUrl(); + $url_for_bc_route = Url::fromRoute('entity.block.off_canvas_form', ['block' => $block->id()])->toString(TRUE)->getGeneratedUrl(); + $this->assertSame($url_for_current_route, $url_for_bc_route); + } + +} diff --git a/core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayBlockFormTest.php b/core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayBlockFormTest.php index 88a2f81..ab07572 100644 --- a/core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayBlockFormTest.php +++ b/core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayBlockFormTest.php @@ -85,7 +85,7 @@ public function testBlocks($theme, $block_plugin, $new_page_text, $element_selec $link = $page->find('css', "$block_selector .contextual-links li a"); $this->assertEquals('Quick edit', $link->getText(), "'Quick edit' is the first contextual link for the block."); - $this->assertContains("/admin/structure/block/manage/$block_id/off-canvas?destination=user/2", $link->getAttribute('href')); + $this->assertContains("/admin/structure/block/manage/$block_id/settings-tray?destination=user/2", $link->getAttribute('href')); if (isset($toolbar_item)) { // Check that you can open a toolbar tray and it will be closed after @@ -418,7 +418,8 @@ protected function assertEditModeEnabled() { $web_assert->elementNotExists('css', '.contextual .trigger.visually-hidden'); // The toolbar edit button should read "Editing". $web_assert->elementContains('css', static::TOOLBAR_EDIT_LINK_SELECTOR, 'Editing'); - // The main canvas element should have the "js-settings-tray-edit-mode" class. + // The main canvas element should have the "js-settings-tray-edit-mode" + // class. $web_assert->elementExists('css', '.dialog-off-canvas-main-canvas.js-settings-tray-edit-mode'); } @@ -522,7 +523,7 @@ public function testCustomBlockLinks() { $href = array_search('Quick edit', $link_labels); $this->assertEquals('', $href); $href = array_search('Quick edit settings', $link_labels); - $this->assertTrue(strstr($href, '/admin/structure/block/manage/custom/off-canvas?destination=user/2') !== FALSE); + $this->assertTrue(strstr($href, '/admin/structure/block/manage/custom/settings-tray?destination=user/2') !== FALSE); } /** @@ -687,6 +688,7 @@ public function testOverriddenConfigurationRemoved() { $this->assertEquals($menu_without_overrides, \Drupal::configFactory()->getEditable('system.menu.main')->get()); $web_assert->pageTextContains('This is on the menu'); } + /** * Asserts that an overridden block has Settings Tray disabled. *