Pull merge.
[yaffs-website] / web / core / modules / block / src / BlockListBuilder.php
1 <?php
2
3 namespace Drupal\block;
4
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;
16 use Drupal\Core\Url;
17 use Symfony\Component\DependencyInjection\ContainerInterface;
18 use Symfony\Component\HttpFoundation\Request;
19
20 /**
21  * Defines a class to build a listing of block entities.
22  *
23  * @see \Drupal\block\Entity\Block
24  */
25 class BlockListBuilder extends ConfigEntityListBuilder implements FormInterface {
26
27   /**
28    * The theme containing the blocks.
29    *
30    * @var string
31    */
32   protected $theme;
33
34   /**
35    * The current request.
36    *
37    * @var \Symfony\Component\HttpFoundation\Request
38    */
39   protected $request;
40
41   /**
42    * The theme manager.
43    *
44    * @var \Drupal\Core\Theme\ThemeManagerInterface
45    */
46   protected $themeManager;
47
48   /**
49    * The form builder.
50    *
51    * @var \Drupal\Core\Form\FormBuilderInterface
52    */
53   protected $formBuilder;
54
55   /**
56    * {@inheritdoc}
57    */
58   protected $limit = FALSE;
59
60   /**
61    * The messenger.
62    *
63    * @var \Drupal\Core\Messenger\MessengerInterface
64    */
65   protected $messenger;
66
67   /**
68    * Constructs a new BlockListBuilder object.
69    *
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
75    *   The theme manager.
76    * @param \Drupal\Core\Form\FormBuilderInterface $form_builder
77    *   The form builder.
78    */
79   public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, ThemeManagerInterface $theme_manager, FormBuilderInterface $form_builder, MessengerInterface $messenger) {
80     parent::__construct($entity_type, $storage);
81
82     $this->themeManager = $theme_manager;
83     $this->formBuilder = $form_builder;
84     $this->messenger = $messenger;
85   }
86
87   /**
88    * {@inheritdoc}
89    */
90   public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
91     return new static(
92       $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')
97     );
98   }
99
100   /**
101    * {@inheritdoc}
102    *
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.
108    *
109    * @return array
110    *   The block list as a renderable array.
111    */
112   public function render($theme = NULL, Request $request = NULL) {
113     $this->request = $request;
114     $this->theme = $theme;
115
116     return $this->formBuilder->getForm($this);
117   }
118
119   /**
120    * {@inheritdoc}
121    */
122   public function getFormId() {
123     return 'block_admin_display_form';
124   }
125
126   /**
127    * {@inheritdoc}
128    */
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';
134
135     // Build the form tree.
136     $form['blocks'] = $this->buildBlocksForm();
137
138     $form['actions'] = [
139       '#tree' => FALSE,
140       '#type' => 'actions',
141     ];
142     $form['actions']['submit'] = [
143       '#type' => 'submit',
144       '#value' => $this->t('Save blocks'),
145       '#button_type' => 'primary',
146     ];
147
148     return $form;
149   }
150
151   /**
152    * Builds the main "Blocks" portion of the form.
153    *
154    * @return array
155    */
156   protected function buildBlocksForm() {
157     // Build blocks first for each region.
158     $blocks = [];
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(),
167         'entity' => $entity,
168         'category' => $definition['category'],
169         'status' => $entity->status(),
170       ];
171     }
172
173     $form = [
174       '#type' => 'table',
175       '#header' => [
176         $this->t('Block'),
177         $this->t('Category'),
178         $this->t('Region'),
179         $this->t('Weight'),
180         $this->t('Operations'),
181       ],
182       '#attributes' => [
183         'id' => 'blocks',
184       ],
185     ];
186
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);
191
192     $placement = FALSE;
193     if ($this->request->query->has('block-placement')) {
194       $placement = $this->request->query->get('block-placement');
195       $form['#attached']['drupalSettings']['blockPlacement'] = $placement;
196     }
197
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'][] = [
202         'action' => 'match',
203         'relationship' => 'sibling',
204         'group' => 'block-region-select',
205         'subgroup' => 'block-region-' . $region,
206         'hidden' => FALSE,
207       ];
208       $form['#tabledrag'][] = [
209         'action' => 'order',
210         'relationship' => 'sibling',
211         'group' => 'block-weight',
212         'subgroup' => 'block-weight-' . $region,
213       ];
214
215       $form['region-' . $region] = [
216         '#attributes' => [
217           'class' => ['region-title', 'region-title-' . $region],
218           'no_striping' => TRUE,
219         ],
220       ];
221       $form['region-' . $region]['title'] = [
222         '#theme_wrappers' => [
223           'container' => [
224             '#attributes' => ['class' => 'region-title__action'],
225           ],
226         ],
227         '#prefix' => $title,
228         '#type' => 'link',
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' => [
232           'colspan' => 5,
233         ],
234         '#attributes' => [
235           'class' => ['use-ajax', 'button', 'button--small'],
236           'data-dialog-type' => 'modal',
237           'data-dialog-options' => Json::encode([
238             'width' => 700,
239           ]),
240         ],
241       ];
242
243       $form['region-' . $region . '-message'] = [
244         '#attributes' => [
245           'class' => [
246             'region-message',
247             'region-' . $region . '-message',
248             empty($blocks[$region]) ? 'region-empty' : 'region-populated',
249           ],
250         ],
251       ];
252       $form['region-' . $region . '-message']['message'] = [
253         '#markup' => '<em>' . $this->t('No blocks in this region') . '</em>',
254         '#wrapper_attributes' => [
255           'colspan' => 5,
256         ],
257       ];
258
259       if (isset($blocks[$region])) {
260         foreach ($blocks[$region] as $info) {
261           $entity_id = $info['entity_id'];
262
263           $form[$entity_id] = [
264             '#attributes' => [
265               'class' => ['draggable'],
266             ],
267           ];
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';
272           }
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'],
277             ],
278           ];
279           $form[$entity_id]['type'] = [
280             '#markup' => $info['category'],
281           ];
282           $form[$entity_id]['region-theme']['region'] = [
283             '#type' => 'select',
284             '#default_value' => $region,
285             '#required' => TRUE,
286             '#title' => $this->t('Region for @block block', ['@block' => $info['label']]),
287             '#title_display' => 'invisible',
288             '#options' => $regions,
289             '#attributes' => [
290               'class' => ['block-region-select', 'block-region-' . $region],
291             ],
292             '#parents' => ['blocks', $entity_id, 'region'],
293           ];
294           $form[$entity_id]['region-theme']['theme'] = [
295             '#type' => 'hidden',
296             '#value' => $this->getThemeName(),
297             '#parents' => ['blocks', $entity_id, 'theme'],
298           ];
299           $form[$entity_id]['weight'] = [
300             '#type' => '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',
305             '#attributes' => [
306               'class' => ['block-weight', 'block-weight-' . $region],
307             ],
308           ];
309           $form[$entity_id]['operations'] = $this->buildOperations($info['entity']);
310         }
311       }
312     }
313
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;
317     }
318     return $form;
319   }
320
321   /**
322    * Gets the name of the theme used for this block listing.
323    *
324    * @return string
325    *   The name of the theme.
326    */
327   protected function getThemeName() {
328     // If no theme was specified, use the current theme.
329     if (!$this->theme) {
330       $this->theme = $this->themeManager->getActiveTheme()->getName();
331     }
332     return $this->theme;
333   }
334
335   /**
336    * {@inheritdoc}
337    */
338   protected function getEntityIds() {
339     return $this->getStorage()->getQuery()
340       ->condition('theme', $this->getThemeName())
341       ->sort($this->entityType->getKey('id'))
342       ->execute();
343   }
344
345   /**
346    * {@inheritdoc}
347    */
348   public function getDefaultOperations(EntityInterface $entity) {
349     $operations = parent::getDefaultOperations($entity);
350
351     if (isset($operations['edit'])) {
352       $operations['edit']['title'] = $this->t('Configure');
353     }
354
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'];
365
366         $destinationUrl = Url::fromUserInput($destination);
367         $destinationQuery = $destinationUrl->getOption('query');
368         unset($destinationQuery['block-placement']);
369
370         $destinationUrl->setOption('query', $destinationQuery);
371         $query['destination'] = $destinationUrl->toString();
372         $url->setOption('query', $query);
373       }
374     }
375     return $operations;
376   }
377
378   /**
379    * {@inheritdoc}
380    */
381   public function validateForm(array &$form, FormStateInterface $form_state) {
382     // No validation.
383   }
384
385   /**
386    * {@inheritdoc}
387    */
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']);
395       $entity->save();
396     }
397     $this->messenger->addStatus($this->t('The block settings have been updated.'));
398
399     // Remove any previously set block placement.
400     $this->request->query->remove('block-placement');
401   }
402
403   /**
404    * Wraps system_region_list().
405    */
406   protected function systemRegionList($theme, $show = REGIONS_ALL) {
407     return system_region_list($theme, $show);
408   }
409
410 }