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\Theme\ThemeManagerInterface;
16 use Symfony\Component\DependencyInjection\ContainerInterface;
17 use Symfony\Component\HttpFoundation\Request;
20 * Defines a class to build a listing of block entities.
22 * @see \Drupal\block\Entity\Block
24 class BlockListBuilder extends ConfigEntityListBuilder implements FormInterface {
27 * The theme containing the blocks.
34 * The current request.
36 * @var \Symfony\Component\HttpFoundation\Request
43 * @var \Drupal\Core\Theme\ThemeManagerInterface
45 protected $themeManager;
50 * @var \Drupal\Core\Form\FormBuilderInterface
52 protected $formBuilder;
57 protected $limit = FALSE;
60 * Constructs a new BlockListBuilder object.
62 * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
63 * The entity type definition.
64 * @param \Drupal\Core\Entity\EntityStorageInterface $storage
65 * The entity storage class.
66 * @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager
68 * @param \Drupal\Core\Form\FormBuilderInterface $form_builder
71 public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, ThemeManagerInterface $theme_manager, FormBuilderInterface $form_builder) {
72 parent::__construct($entity_type, $storage);
74 $this->themeManager = $theme_manager;
75 $this->formBuilder = $form_builder;
81 public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
84 $container->get('entity.manager')->getStorage($entity_type->id()),
85 $container->get('theme.manager'),
86 $container->get('form_builder')
93 * @param string|null $theme
94 * (optional) The theme to display the blocks for. If NULL, the current
96 * @param \Symfony\Component\HttpFoundation\Request $request
97 * The current request.
100 * The block list as a renderable array.
102 public function render($theme = NULL, Request $request = NULL) {
103 $this->request = $request;
104 $this->theme = $theme;
106 return $this->formBuilder->getForm($this);
112 public function getFormId() {
113 return 'block_admin_display_form';
119 public function buildForm(array $form, FormStateInterface $form_state) {
120 $form['#attached']['library'][] = 'core/drupal.tableheader';
121 $form['#attached']['library'][] = 'block/drupal.block';
122 $form['#attached']['library'][] = 'block/drupal.block.admin';
123 $form['#attributes']['class'][] = 'clearfix';
125 // Build the form tree.
126 $form['blocks'] = $this->buildBlocksForm();
130 '#type' => 'actions',
132 $form['actions']['submit'] = [
134 '#value' => $this->t('Save blocks'),
135 '#button_type' => 'primary',
142 * Builds the main "Blocks" portion of the form.
146 protected function buildBlocksForm() {
147 // Build blocks first for each region.
149 $entities = $this->load();
150 /** @var \Drupal\block\BlockInterface[] $entities */
151 foreach ($entities as $entity_id => $entity) {
152 $definition = $entity->getPlugin()->getPluginDefinition();
153 $blocks[$entity->getRegion()][$entity_id] = [
154 'label' => $entity->label(),
155 'entity_id' => $entity_id,
156 'weight' => $entity->getWeight(),
158 'category' => $definition['category'],
159 'status' => $entity->status(),
167 $this->t('Category'),
170 $this->t('Operations'),
177 // Weights range from -delta to +delta, so delta should be at least half
178 // of the amount of blocks present. This makes sure all blocks in the same
179 // region get an unique weight.
180 $weight_delta = round(count($entities) / 2);
183 if ($this->request->query->has('block-placement')) {
184 $placement = $this->request->query->get('block-placement');
185 $form['#attached']['drupalSettings']['blockPlacement'] = $placement;
188 // Loop over each region and build blocks.
189 $regions = $this->systemRegionList($this->getThemeName(), REGIONS_VISIBLE);
190 foreach ($regions as $region => $title) {
191 $form['#tabledrag'][] = [
193 'relationship' => 'sibling',
194 'group' => 'block-region-select',
195 'subgroup' => 'block-region-' . $region,
198 $form['#tabledrag'][] = [
200 'relationship' => 'sibling',
201 'group' => 'block-weight',
202 'subgroup' => 'block-weight-' . $region,
205 $form['region-' . $region] = [
207 'class' => ['region-title', 'region-title-' . $region],
208 'no_striping' => TRUE,
211 $form['region-' . $region]['title'] = [
212 '#theme_wrappers' => [
214 '#attributes' => ['class' => 'region-title__action'],
219 '#title' => $this->t('Place block <span class="visually-hidden">in the %region region</span>', ['%region' => $title]),
220 '#url' => Url::fromRoute('block.admin_library', ['theme' => $this->getThemeName()], ['query' => ['region' => $region]]),
221 '#wrapper_attributes' => [
225 'class' => ['use-ajax', 'button', 'button--small'],
226 'data-dialog-type' => 'modal',
227 'data-dialog-options' => Json::encode([
233 $form['region-' . $region . '-message'] = [
237 'region-' . $region . '-message',
238 empty($blocks[$region]) ? 'region-empty' : 'region-populated',
242 $form['region-' . $region . '-message']['message'] = [
243 '#markup' => '<em>' . $this->t('No blocks in this region') . '</em>',
244 '#wrapper_attributes' => [
249 if (isset($blocks[$region])) {
250 foreach ($blocks[$region] as $info) {
251 $entity_id = $info['entity_id'];
253 $form[$entity_id] = [
255 'class' => ['draggable'],
258 $form[$entity_id]['#attributes']['class'][] = $info['status'] ? 'block-enabled' : 'block-disabled';
259 if ($placement && $placement == Html::getClass($entity_id)) {
260 $form[$entity_id]['#attributes']['class'][] = 'color-success';
261 $form[$entity_id]['#attributes']['class'][] = 'js-block-placed';
263 $form[$entity_id]['info'] = [
264 '#plain_text' => $info['status'] ? $info['label'] : $this->t('@label (disabled)', ['@label' => $info['label']]),
265 '#wrapper_attributes' => [
266 'class' => ['block'],
269 $form[$entity_id]['type'] = [
270 '#markup' => $info['category'],
272 $form[$entity_id]['region-theme']['region'] = [
274 '#default_value' => $region,
276 '#title' => $this->t('Region for @block block', ['@block' => $info['label']]),
277 '#title_display' => 'invisible',
278 '#options' => $regions,
280 'class' => ['block-region-select', 'block-region-' . $region],
282 '#parents' => ['blocks', $entity_id, 'region'],
284 $form[$entity_id]['region-theme']['theme'] = [
286 '#value' => $this->getThemeName(),
287 '#parents' => ['blocks', $entity_id, 'theme'],
289 $form[$entity_id]['weight'] = [
291 '#default_value' => $info['weight'],
292 '#delta' => $weight_delta,
293 '#title' => $this->t('Weight for @block block', ['@block' => $info['label']]),
294 '#title_display' => 'invisible',
296 'class' => ['block-weight', 'block-weight-' . $region],
299 $form[$entity_id]['operations'] = $this->buildOperations($info['entity']);
304 // Do not allow disabling the main system content block when it is present.
305 if (isset($form['system_main']['region'])) {
306 $form['system_main']['region']['#required'] = TRUE;
312 * Gets the name of the theme used for this block listing.
315 * The name of the theme.
317 protected function getThemeName() {
318 // If no theme was specified, use the current theme.
320 $this->theme = $this->themeManager->getActiveTheme()->getName();
328 protected function getEntityIds() {
329 return $this->getStorage()->getQuery()
330 ->condition('theme', $this->getThemeName())
331 ->sort($this->entityType->getKey('id'))
338 public function getDefaultOperations(EntityInterface $entity) {
339 $operations = parent::getDefaultOperations($entity);
341 if (isset($operations['edit'])) {
342 $operations['edit']['title'] = $this->t('Configure');
345 if (isset($operations['delete'])) {
346 $operations['delete']['title'] = $this->t('Remove');
354 public function validateForm(array &$form, FormStateInterface $form_state) {
361 public function submitForm(array &$form, FormStateInterface $form_state) {
362 $entities = $this->storage->loadMultiple(array_keys($form_state->getValue('blocks')));
363 /** @var \Drupal\block\BlockInterface[] $entities */
364 foreach ($entities as $entity_id => $entity) {
365 $entity_values = $form_state->getValue(['blocks', $entity_id]);
366 $entity->setWeight($entity_values['weight']);
367 $entity->setRegion($entity_values['region']);
370 drupal_set_message(t('The block settings have been updated.'));
372 // Remove any previously set block placement.
373 $this->request->query->remove('block-placement');
377 * Wraps system_region_list().
379 protected function systemRegionList($theme, $show = REGIONS_ALL) {
380 return system_region_list($theme, $show);