settings_tray.module 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. <?php
  2. /**
  3. * @file
  4. * Allows configuring blocks and other configuration from the site front-end.
  5. */
  6. use Drupal\Core\Url;
  7. use Drupal\Core\Asset\AttachedAssetsInterface;
  8. use Drupal\Core\Routing\RouteMatchInterface;
  9. use Drupal\block\entity\Block;
  10. use Drupal\block\BlockInterface;
  11. use Drupal\settings_tray\Block\BlockEntitySettingTrayForm;
  12. /**
  13. * Implements hook_help().
  14. */
  15. function settings_tray_help($route_name, RouteMatchInterface $route_match) {
  16. switch ($route_name) {
  17. case 'help.page.settings_tray':
  18. $output = '<h3>' . t('About') . '</h3>';
  19. $output .= '<p>' . t('The Settings Tray module allows users with the <a href=":administer_block_permission">Administer blocks</a> and <a href=":contextual_permission">Use contextual links</a> permissions to edit blocks without visiting a separate page. For more information, see the <a href=":handbook_url">online documentation for the Settings Tray module</a>.', [':handbook_url' => 'https://www.drupal.org/documentation/modules/settings_tray', ':administer_block_permission' => Url::fromRoute('user.admin_permissions', [], ['fragment' => 'module-block'])->toString(), ':contextual_permission' => Url::fromRoute('user.admin_permissions', [], ['fragment' => 'module-contextual'])->toString()]) . '</p>';
  20. $output .= '<h3>' . t('Uses') . '</h3>';
  21. $output .= '<dl>';
  22. $output .= '<dt>' . t('Editing blocks in place') . '</dt>';
  23. $output .= '<dd>';
  24. $output .= '<p>' . t('To edit blocks in place, either click the <strong>Edit</strong> button in the toolbar and then click on the block, or choose "Quick edit" from the block\'s contextual link. (See the <a href=":contextual">Contextual Links module help</a> for more information about how to use contextual links.)', [':contextual' => Url::fromRoute('help.page', ['name' => 'contextual'])->toString()]) . '</p>';
  25. $output .= '<p>' . t('The Settings Tray for the block will open in a sidebar, with a compact form for configuring what the block shows.') . '</p>';
  26. $output .= '<p>' . t('Save the form and the changes will be immediately visible on the page.') . '</p>';
  27. $output .= '</dd>';
  28. $output .= '</dl>';
  29. return ['#markup' => $output];
  30. }
  31. }
  32. /**
  33. * Implements hook_contextual_links_view_alter().
  34. *
  35. * Change Configure Blocks into off_canvas links.
  36. */
  37. function settings_tray_contextual_links_view_alter(&$element, $items) {
  38. if (isset($element['#links']['settings-trayblock-configure'])) {
  39. // Place settings_tray link first.
  40. $settings_tray_link = $element['#links']['settings-trayblock-configure'];
  41. unset($element['#links']['settings-trayblock-configure']);
  42. $element['#links'] = ['settings-trayblock-configure' => $settings_tray_link] + $element['#links'];
  43. // If this is content block change title to avoid duplicate "Quick Edit".
  44. if (isset($element['#links']['block-contentblock-edit'])) {
  45. $element['#links']['settings-trayblock-configure']['title'] = t('Quick edit settings');
  46. }
  47. $element['#attached']['library'][] = 'core/drupal.dialog.off_canvas';
  48. }
  49. }
  50. /**
  51. * Checks if a block has overrides.
  52. *
  53. * @param \Drupal\block\BlockInterface $block
  54. * The block to check for overrides.
  55. *
  56. * @return bool
  57. * TRUE if the block has overrides otherwise FALSE.
  58. *
  59. * @internal
  60. */
  61. function _settings_tray_has_block_overrides(BlockInterface $block) {
  62. // @todo Replace the following with $block->hasOverrides() in https://www.drupal.org/project/drupal/issues/2910353
  63. // and remove this function.
  64. return \Drupal::config($block->getEntityType()->getConfigPrefix() . '.' . $block->id())->hasOverrides();
  65. }
  66. /**
  67. * Implements hook_block_view_alter().
  68. */
  69. function settings_tray_block_view_alter(array &$build) {
  70. if (isset($build['#contextual_links']['block'])) {
  71. // Ensure that contextual links vary by whether the block has config overrides
  72. // or not.
  73. // @see _contextual_links_to_id()
  74. $build['#contextual_links']['block']['metadata']['has_overrides'] = _settings_tray_has_block_overrides($build['#block']) ? 1 : 0;
  75. }
  76. // Force a new 'data-contextual-id' attribute on blocks when this module is
  77. // enabled so as not to reuse stale data cached client-side.
  78. // @todo Remove when https://www.drupal.org/node/2773591 is fixed.
  79. $build['#contextual_links']['settings_tray'] = [
  80. 'route_parameters' => [],
  81. ];
  82. }
  83. /**
  84. * Implements hook_entity_type_build().
  85. */
  86. function settings_tray_entity_type_build(array &$entity_types) {
  87. /* @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
  88. $entity_types['block']
  89. ->setFormClass('settings_tray', BlockEntitySettingTrayForm::class)
  90. ->setLinkTemplate('settings_tray-form', '/admin/structure/block/manage/{block}/settings-tray');
  91. }
  92. /**
  93. * Implements hook_preprocess_HOOK() for block templates.
  94. */
  95. function settings_tray_preprocess_block(&$variables) {
  96. // Only blocks that have a settings_tray form and have no configuration
  97. // overrides will have a "Quick Edit" link. We could wait for the contextual
  98. // links to be initialized on the client side, and then add the class and
  99. // data- attribute below there (via JavaScript). But that would mean that it
  100. // would be impossible to show Settings Tray's clickable regions immediately
  101. // when the page loads. When latency is high, this will cause flicker.
  102. // @see \Drupal\settings_tray\Access\BlockPluginHasSettingsTrayFormAccessCheck
  103. /** @var \Drupal\settings_tray\Access\BlockPluginHasSettingsTrayFormAccessCheck $access_checker */
  104. $access_checker = \Drupal::service('access_check.settings_tray.block.settings_tray_form');
  105. /** @var \Drupal\Core\Block\BlockManagerInterface $block_plugin_manager */
  106. $block_plugin_manager = \Drupal::service('plugin.manager.block');
  107. /** @var \Drupal\Core\Block\BlockPluginInterface $block_plugin */
  108. $block_plugin = $block_plugin_manager->createInstance($variables['plugin_id']);
  109. if (isset($variables['elements']['#contextual_links']['block']['route_parameters']['block'])) {
  110. $block = Block::load($variables['elements']['#contextual_links']['block']['route_parameters']['block']);
  111. if ($access_checker->accessBlockPlugin($block_plugin)->isAllowed() && !_settings_tray_has_block_overrides($block)) {
  112. // Add class and attributes to all blocks to allow Javascript to target.
  113. $variables['attributes']['class'][] = 'settings-tray-editable';
  114. $variables['attributes']['data-drupal-settingstray'] = 'editable';
  115. }
  116. }
  117. }
  118. /**
  119. * Implements hook_toolbar_alter().
  120. *
  121. * Alters the 'contextual' toolbar tab if it exists (meaning the user is allowed
  122. * to use contextual links) and if they can administer blocks.
  123. *
  124. * @todo Remove the "administer blocks" requirement in
  125. * https://www.drupal.org/node/2822965.
  126. *
  127. * @see contextual_toolbar()
  128. */
  129. function settings_tray_toolbar_alter(&$items) {
  130. $items['contextual']['#cache']['contexts'][] = 'user.permissions';
  131. if (isset($items['contextual']['tab']) && \Drupal::currentUser()->hasPermission('administer blocks')) {
  132. $items['contextual']['#weight'] = -1000;
  133. $items['contextual']['#attached']['library'][] = 'settings_tray/drupal.settings_tray';
  134. $items['contextual']['tab']['#attributes']['data-drupal-settingstray'] = 'toggle';
  135. // Set a class on items to mark whether they should be active in edit mode.
  136. // @todo Create a dynamic method for modules to set their own items.
  137. // https://www.drupal.org/node/2784589.
  138. $edit_mode_items = ['contextual', 'block_place'];
  139. foreach ($items as $key => $item) {
  140. if (!in_array($key, $edit_mode_items) && (!isset($items[$key]['#wrapper_attributes']['class']) || !in_array('hidden', $items[$key]['#wrapper_attributes']['class']))) {
  141. $items[$key]['#wrapper_attributes']['class'][] = 'edit-mode-inactive';
  142. }
  143. }
  144. }
  145. }
  146. /**
  147. * Implements hook_block_alter().
  148. *
  149. * Ensures every block plugin definition has an 'settings_tray' form specified.
  150. *
  151. * @see \Drupal\settings_tray\Access\BlockPluginHasSettingsTrayFormAccessCheck
  152. */
  153. function settings_tray_block_alter(&$definitions) {
  154. foreach ($definitions as &$definition) {
  155. // If a block plugin does not define its own 'settings_tray' form, use the
  156. // plugin class itself.
  157. if (!isset($definition['forms']['settings_tray'])) {
  158. $definition['forms']['settings_tray'] = $definition['class'];
  159. }
  160. }
  161. }
  162. /**
  163. * Implements hook_css_alter().
  164. */
  165. function settings_tray_css_alter(&$css, AttachedAssetsInterface $assets) {
  166. // @todo Remove once conditional ordering is introduced in
  167. // https://www.drupal.org/node/1945262.
  168. $path = drupal_get_path('module', 'settings_tray') . '/css/settings_tray.theme.css';
  169. if (isset($css[$path])) {
  170. // Use 200 to come after CSS_AGGREGATE_THEME.
  171. $css[$path]['group'] = 200;
  172. }
  173. }