3 namespace Drupal\block;
5 use Drupal\Component\Utility\Html;
6 use Drupal\Component\Serialization\Json;
7 use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
8 use Drupal\Core\Entity\EntityInterface;
9 use Drupal\Core\Entity\EntityStorageInterface;
10 use Drupal\Core\Entity\EntityTypeInterface;
11 use Drupal\Core\Form\FormBuilderInterface;
12 use Drupal\Core\Form\FormInterface;
13 use Drupal\Core\Form\FormStateInterface;
14 use Drupal\Core\Messenger\MessengerInterface;
15 use Drupal\Core\Theme\ThemeManagerInterface;
17 use Symfony\Component\DependencyInjection\ContainerInterface;
18 use Symfony\Component\HttpFoundation\Request;
21 * Defines a class to build a listing of block entities.
23 * @see \Drupal\block\Entity\Block
25 class BlockListBuilder extends ConfigEntityListBuilder implements FormInterface {
28 * The theme containing the blocks.
35 * The current request.
37 * @var \Symfony\Component\HttpFoundation\Request
44 * @var \Drupal\Core\Theme\ThemeManagerInterface
46 protected $themeManager;
51 * @var \Drupal\Core\Form\FormBuilderInterface
53 protected $formBuilder;
58 protected $limit = FALSE;
63 * @var \Drupal\Core\Messenger\MessengerInterface
68 * Constructs a new BlockListBuilder object.
70 * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
71 * The entity type definition.
72 * @param \Drupal\Core\Entity\EntityStorageInterface $storage
73 * The entity storage class.
74 * @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager
76 * @param \Drupal\Core\Form\FormBuilderInterface $form_builder
79 public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, ThemeManagerInterface $theme_manager, FormBuilderInterface $form_builder, MessengerInterface $messenger) {
80 parent::__construct($entity_type, $storage);
82 $this->themeManager = $theme_manager;
83 $this->formBuilder = $form_builder;
84 $this->messenger = $messenger;
90 public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
93 $container->get('entity.manager')->getStorage($entity_type->id()),
94 $container->get('theme.manager'),
95 $container->get('form_builder'),
96 $container->get('messenger')
103 * @param string|null $theme
104 * (optional) The theme to display the blocks for. If NULL, the current
105 * theme will be used.
106 * @param \Symfony\Component\HttpFoundation\Request $request
107 * The current request.
110 * The block list as a renderable array.
112 public function render($theme = NULL, Request $request = NULL) {
113 $this->request = $request;
114 $this->theme = $theme;
116 return $this->formBuilder->getForm($this);
122 public function getFormId() {
123 return 'block_admin_display_form';
129 public function buildForm(array $form, FormStateInterface $form_state) {
130 $form['#attached']['library'][] = 'core/drupal.tableheader';
131 $form['#attached']['library'][] = 'block/drupal.block';
132 $form['#attached']['library'][] = 'block/drupal.block.admin';
133 $form['#attributes']['class'][] = 'clearfix';
135 // Build the form tree.
136 $form['blocks'] = $this->buildBlocksForm();
140 '#type' => 'actions',
142 $form['actions']['submit'] = [
144 '#value' => $this->t('Save blocks'),
145 '#button_type' => 'primary',
152 * Builds the main "Blocks" portion of the form.
156 protected function buildBlocksForm() {
157 // Build blocks first for each region.
159 $entities = $this->load();
160 /** @var \Drupal\block\BlockInterface[] $entities */
161 foreach ($entities as $entity_id => $entity) {
162 $definition = $entity->getPlugin()->getPluginDefinition();
163 $blocks[$entity->getRegion()][$entity_id] = [
164 'label' => $entity->label(),
165 'entity_id' => $entity_id,
166 'weight' => $entity->getWeight(),
168 'category' => $definition['category'],
169 'status' => $entity->status(),
177 $this->t('Category'),
180 $this->t('Operations'),
187 // Weights range from -delta to +delta, so delta should be at least half
188 // of the amount of blocks present. This makes sure all blocks in the same
189 // region get an unique weight.
190 $weight_delta = round(count($entities) / 2);
193 if ($this->request->query->has('block-placement')) {
194 $placement = $this->request->query->get('block-placement');
195 $form['#attached']['drupalSettings']['blockPlacement'] = $placement;
198 // Loop over each region and build blocks.
199 $regions = $this->systemRegionList($this->getThemeName(), REGIONS_VISIBLE);
200 foreach ($regions as $region => $title) {
201 $form['#tabledrag'][] = [
203 'relationship' => 'sibling',
204 'group' => 'block-region-select',
205 'subgroup' => 'block-region-' . $region,
208 $form['#tabledrag'][] = [
210 'relationship' => 'sibling',
211 'group' => 'block-weight',
212 'subgroup' => 'block-weight-' . $region,
215 $form['region-' . $region] = [
217 'class' => ['region-title', 'region-title-' . $region],
218 'no_striping' => TRUE,
221 $form['region-' . $region]['title'] = [
222 '#theme_wrappers' => [
224 '#attributes' => ['class' => 'region-title__action'],
229 '#title' => $this->t('Place block <span class="visually-hidden">in the %region region</span>', ['%region' => $title]),
230 '#url' => Url::fromRoute('block.admin_library', ['theme' => $this->getThemeName()], ['query' => ['region' => $region]]),
231 '#wrapper_attributes' => [
235 'class' => ['use-ajax', 'button', 'button--small'],
236 'data-dialog-type' => 'modal',
237 'data-dialog-options' => Json::encode([
243 $form['region-' . $region . '-message'] = [
247 'region-' . $region . '-message',
248 empty($blocks[$region]) ? 'region-empty' : 'region-populated',
252 $form['region-' . $region . '-message']['message'] = [
253 '#markup' => '<em>' . $this->t('No blocks in this region') . '</em>',
254 '#wrapper_attributes' => [
259 if (isset($blocks[$region])) {
260 foreach ($blocks[$region] as $info) {
261 $entity_id = $info['entity_id'];
263 $form[$entity_id] = [
265 'class' => ['draggable'],
268 $form[$entity_id]['#attributes']['class'][] = $info['status'] ? 'block-enabled' : 'block-disabled';
269 if ($placement && $placement == Html::getClass($entity_id)) {
270 $form[$entity_id]['#attributes']['class'][] = 'color-success';
271 $form[$entity_id]['#attributes']['class'][] = 'js-block-placed';
273 $form[$entity_id]['info'] = [
274 '#plain_text' => $info['status'] ? $info['label'] : $this->t('@label (disabled)', ['@label' => $info['label']]),
275 '#wrapper_attributes' => [
276 'class' => ['block'],
279 $form[$entity_id]['type'] = [
280 '#markup' => $info['category'],
282 $form[$entity_id]['region-theme']['region'] = [
284 '#default_value' => $region,
286 '#title' => $this->t('Region for @block block', ['@block' => $info['label']]),
287 '#title_display' => 'invisible',
288 '#options' => $regions,
290 'class' => ['block-region-select', 'block-region-' . $region],
292 '#parents' => ['blocks', $entity_id, 'region'],
294 $form[$entity_id]['region-theme']['theme'] = [
296 '#value' => $this->getThemeName(),
297 '#parents' => ['blocks', $entity_id, 'theme'],
299 $form[$entity_id]['weight'] = [
301 '#default_value' => $info['weight'],
302 '#delta' => $weight_delta,
303 '#title' => $this->t('Weight for @block block', ['@block' => $info['label']]),
304 '#title_display' => 'invisible',
306 'class' => ['block-weight', 'block-weight-' . $region],
309 $form[$entity_id]['operations'] = $this->buildOperations($info['entity']);
314 // Do not allow disabling the main system content block when it is present.
315 if (isset($form['system_main']['region'])) {
316 $form['system_main']['region']['#required'] = TRUE;
322 * Gets the name of the theme used for this block listing.
325 * The name of the theme.
327 protected function getThemeName() {
328 // If no theme was specified, use the current theme.
330 $this->theme = $this->themeManager->getActiveTheme()->getName();
338 protected function getEntityIds() {
339 return $this->getStorage()->getQuery()
340 ->condition('theme', $this->getThemeName())
341 ->sort($this->entityType->getKey('id'))
348 public function getDefaultOperations(EntityInterface $entity) {
349 $operations = parent::getDefaultOperations($entity);
351 if (isset($operations['edit'])) {
352 $operations['edit']['title'] = $this->t('Configure');
355 if (isset($operations['delete'])) {
356 $operations['delete']['title'] = $this->t('Remove');
357 // Block operation links should have the `block-placement` query string
358 // parameter removed to ensure that JavaScript does not receive a block
359 // name that has been recently removed.
360 foreach ($operations as $operation) {
361 /** @var \Drupal\Core\Url $url */
362 $url = $operation['url'];
363 $query = $url->getOption('query');
364 $destination = $query['destination'];
366 $destinationUrl = Url::fromUserInput($destination);
367 $destinationQuery = $destinationUrl->getOption('query');
368 unset($destinationQuery['block-placement']);
370 $destinationUrl->setOption('query', $destinationQuery);
371 $query['destination'] = $destinationUrl->toString();
372 $url->setOption('query', $query);
381 public function validateForm(array &$form, FormStateInterface $form_state) {
388 public function submitForm(array &$form, FormStateInterface $form_state) {
389 $entities = $this->storage->loadMultiple(array_keys($form_state->getValue('blocks')));
390 /** @var \Drupal\block\BlockInterface[] $entities */
391 foreach ($entities as $entity_id => $entity) {
392 $entity_values = $form_state->getValue(['blocks', $entity_id]);
393 $entity->setWeight($entity_values['weight']);
394 $entity->setRegion($entity_values['region']);
397 $this->messenger->addStatus($this->t('The block settings have been updated.'));
399 // Remove any previously set block placement.
400 $this->request->query->remove('block-placement');
404 * Wraps system_region_list().
406 protected function systemRegionList($theme, $show = REGIONS_ALL) {
407 return system_region_list($theme, $show);