BlockForm.php 14.4 KB
Newer Older
1 2 3 4
<?php

namespace Drupal\block;

5
use Drupal\Component\Utility\Html;
6
use Drupal\Core\Entity\EntityForm;
7
use Drupal\Core\Entity\EntityManagerInterface;
8
use Drupal\Core\Executable\ExecutableManagerInterface;
9
use Drupal\Core\Extension\ThemeHandlerInterface;
10 11
use Drupal\Core\Form\FormState;
use Drupal\Core\Form\FormStateInterface;
12
use Drupal\Core\Language\LanguageManagerInterface;
13
use Drupal\Core\Plugin\ContextAwarePluginInterface;
14
use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
15
use Symfony\Component\DependencyInjection\ContainerInterface;
16 17

/**
18
 * Provides form for block instance forms.
19
 */
20
class BlockForm extends EntityForm {
21

22 23 24 25 26 27 28
  /**
   * The block entity.
   *
   * @var \Drupal\block\BlockInterface
   */
  protected $entity;

29
  /**
30
   * The block storage.
31
   *
32
   * @var \Drupal\Core\Entity\EntityStorageInterface
33
   */
34
  protected $storage;
35

36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
  /**
   * The condition plugin manager.
   *
   * @var \Drupal\Core\Condition\ConditionManager
   */
  protected $manager;

  /**
   * The event dispatcher service.
   *
   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
   */
  protected $dispatcher;

  /**
   * The language manager service.
   *
   * @var \Drupal\Core\Language\LanguageManagerInterface
   */
  protected $language;

57 58 59 60 61 62 63
  /**
   * The theme handler.
   *
   * @var \Drupal\Core\Extension\ThemeHandler
   */
  protected $themeHandler;

64 65 66 67 68 69 70
  /**
   * The context repository service.
   *
   * @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface
   */
  protected $contextRepository;

71
  /**
72
   * Constructs a BlockForm object.
73
   *
74
   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
75
   *   The entity manager.
76 77
   * @param \Drupal\Core\Executable\ExecutableManagerInterface $manager
   *   The ConditionManager for building the visibility UI.
78 79
   * @param \Drupal\Core\Plugin\Context\ContextRepositoryInterface $context_repository
   *   The lazy context repository service.
80 81
   * @param \Drupal\Core\Language\LanguageManagerInterface $language
   *   The language manager.
82
   * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
83
   *   The theme handler.
84
   */
85
  public function __construct(EntityManagerInterface $entity_manager, ExecutableManagerInterface $manager, ContextRepositoryInterface $context_repository, LanguageManagerInterface $language, ThemeHandlerInterface $theme_handler) {
86
    $this->storage = $entity_manager->getStorage('block');
87
    $this->manager = $manager;
88
    $this->contextRepository = $context_repository;
89
    $this->language = $language;
90
    $this->themeHandler = $theme_handler;
91 92 93 94 95
  }

  /**
   * {@inheritdoc}
   */
96
  public static function create(ContainerInterface $container) {
97
    return new static(
98 99
      $container->get('entity.manager'),
      $container->get('plugin.manager.condition'),
100
      $container->get('context.repository'),
101
      $container->get('language_manager'),
102
      $container->get('theme_handler')
103 104
    );
  }
105 106

  /**
107
   * {@inheritdoc}
108
   */
109
  public function form(array $form, FormStateInterface $form_state) {
110
    $entity = $this->entity;
111 112

    // Store theme settings in $form_state for use below.
113
    if (!$theme = $entity->getTheme()) {
114
      $theme = $this->config('system.theme')->get('default');
115
    }
116
    $form_state->set('block_theme', $theme);
117

118 119
    // Store the gathered contexts in the form state for other objects to use
    // during form building.
120
    $form_state->setTemporaryValue('gathered_contexts', $this->contextRepository->getAvailableContexts());
121

122
    $form['#tree'] = TRUE;
123
    $form['settings'] = $entity->getPlugin()->buildConfigurationForm(array(), $form_state);
124
    $form['visibility'] = $this->buildVisibilityInterface([], $form_state);
125

126
    // If creating a new block, calculate a safe default machine name.
127
    $form['id'] = array(
128 129
      '#type' => 'machine_name',
      '#maxlength' => 64,
130 131
      '#description' => $this->t('A unique name for this block instance. Must be alpha-numeric and underscore separated.'),
      '#default_value' => !$entity->isNew() ? $entity->id() : $this->getUniqueMachineName($entity),
132
      '#machine_name' => array(
133
        'exists' => '\Drupal\block\Entity\Block::load',
134 135 136 137 138 139 140
        'replace_pattern' => '[^a-z0-9_.]+',
        'source' => array('settings', 'label'),
      ),
      '#required' => TRUE,
      '#disabled' => !$entity->isNew(),
    );

141
    // Theme settings.
142
    if ($entity->getTheme()) {
143 144
      $form['theme'] = array(
        '#type' => 'value',
145
        '#value' => $theme,
146 147 148 149
      );
    }
    else {
      $theme_options = array();
150
      foreach ($this->themeHandler->listInfo() as $theme_name => $theme_info) {
151 152 153 154 155 156 157 158 159 160
        if (!empty($theme_info->status)) {
          $theme_options[$theme_name] = $theme_info->info['name'];
        }
      }
      $form['theme'] = array(
        '#type' => 'select',
        '#options' => $theme_options,
        '#title' => t('Theme'),
        '#default_value' => $theme,
        '#ajax' => array(
161
          'callback' => '::themeSwitch',
162 163 164 165
          'wrapper' => 'edit-block-region-wrapper',
        ),
      );
    }
166

167
    // Region settings.
168 169
    $entity_region = $entity->getRegion();
    $region = $entity->isNew() ? $this->getRequest()->query->get('region', $entity_region) : $entity_region;
170 171
    $form['region'] = array(
      '#type' => 'select',
172 173
      '#title' => $this->t('Region'),
      '#description' => $this->t('Select the region where this block should be displayed.'),
174
      '#default_value' => $region,
175
      '#empty_value' => BlockInterface::BLOCK_REGION_NONE,
176 177 178
      '#options' => system_region_list($theme, REGIONS_VISIBLE),
      '#prefix' => '<div id="edit-block-region-wrapper">',
      '#suffix' => '</div>',
179
    );
180
    $form['#attached']['library'][] = 'block/drupal.block.admin';
181
    return $form;
182 183
  }

184 185 186
  /**
   * Handles switching the available regions based on the selected theme.
   */
187
  public function themeSwitch($form, FormStateInterface $form_state) {
188
    $form['region']['#options'] = system_region_list($form_state->getValue('theme'), REGIONS_VISIBLE);
189 190 191
    return $form['region'];
  }

192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
  /**
   * Helper function for building the visibility UI form.
   *
   * @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 array
   *   The form array with the visibility UI added in.
   */
  protected function buildVisibilityInterface(array $form, FormStateInterface $form_state) {
    $form['visibility_tabs'] = [
      '#type' => 'vertical_tabs',
      '#title' => $this->t('Visibility'),
      '#parents' => ['visibility_tabs'],
      '#attached' => [
        'library' => [
          'block/drupal.block',
        ],
      ],
    ];
    // @todo Allow list of conditions to be configured in
215
    //   https://www.drupal.org/node/2284687.
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
    $visibility = $this->entity->getVisibility();
    foreach ($this->manager->getDefinitions() as $condition_id => $definition) {
      // Don't display the current theme condition.
      if ($condition_id == 'current_theme') {
        continue;
      }
      // Don't display the language condition until we have multiple languages.
      if ($condition_id == 'language' && !$this->language->isMultilingual()) {
        continue;
      }
      /** @var \Drupal\Core\Condition\ConditionInterface $condition */
      $condition = $this->manager->createInstance($condition_id, isset($visibility[$condition_id]) ? $visibility[$condition_id] : []);
      $form_state->set(['conditions', $condition_id], $condition);
      $condition_form = $condition->buildConfigurationForm([], $form_state);
      $condition_form['#type'] = 'details';
      $condition_form['#title'] = $condition->getPluginDefinition()['label'];
      $condition_form['#group'] = 'visibility_tabs';
      $form[$condition_id] = $condition_form;
    }

    if (isset($form['node_type'])) {
      $form['node_type']['#title'] = $this->t('Content types');
      $form['node_type']['bundles']['#title'] = $this->t('Content types');
      $form['node_type']['negate']['#type'] = 'value';
      $form['node_type']['negate']['#title_display'] = 'invisible';
      $form['node_type']['negate']['#value'] = $form['node_type']['negate']['#default_value'];
    }
    if (isset($form['user_role'])) {
      $form['user_role']['#title'] = $this->t('Roles');
      unset($form['user_role']['roles']['#description']);
      $form['user_role']['negate']['#type'] = 'value';
      $form['user_role']['negate']['#value'] = $form['user_role']['negate']['#default_value'];
    }
    if (isset($form['request_path'])) {
      $form['request_path']['#title'] = $this->t('Pages');
      $form['request_path']['negate']['#type'] = 'radios';
252
      $form['request_path']['negate']['#default_value'] = (int) $form['request_path']['negate']['#default_value'];
253 254 255 256 257 258 259 260 261 262 263 264 265
      $form['request_path']['negate']['#title_display'] = 'invisible';
      $form['request_path']['negate']['#options'] = [
        $this->t('Show for the listed pages'),
        $this->t('Hide for the listed pages'),
      ];
    }
    if (isset($form['language'])) {
      $form['language']['negate']['#type'] = 'value';
      $form['language']['negate']['#value'] = $form['language']['negate']['#default_value'];
    }
    return $form;
  }

266
  /**
267
   * {@inheritdoc}
268
   */
269
  protected function actions(array $form, FormStateInterface $form_state) {
270
    $actions = parent::actions($form, $form_state);
271
    $actions['submit']['#value'] = $this->t('Save block');
272 273 274 275
    return $actions;
  }

  /**
276
   * {@inheritdoc}
277
   */
278 279
  public function validateForm(array &$form, FormStateInterface $form_state) {
    parent::validateForm($form, $form_state);
280

281 282
    // The Block Entity form puts all block plugin form elements in the
    // settings form element, so just pass that to the block for validation.
283
    $settings = (new FormState())->setValues($form_state->getValue('settings'));
284
    // Call the plugin validate handler.
285
    $this->entity->getPlugin()->validateConfigurationForm($form, $settings);
286
    // Update the original form values.
287
    $form_state->setValue('settings', $settings->getValues());
288 289 290 291 292 293 294 295 296 297 298 299 300 301
    $this->validateVisibility($form, $form_state);
  }

  /**
   * Helper function to independently validate the visibility UI.
   *
   * @param array $form
   *   A nested array form elements comprising the form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   */
  protected function validateVisibility(array $form, FormStateInterface $form_state) {
    // Validate visibility condition settings.
    foreach ($form_state->getValue('visibility') as $condition_id => $values) {
302 303 304 305 306 307 308
      // All condition plugins use 'negate' as a Boolean in their schema.
      // However, certain form elements may return it as 0/1. Cast here to
      // ensure the data is in the expected type.
      if (array_key_exists('negate', $values)) {
        $values['negate'] = (bool) $values['negate'];
      }

309 310 311 312 313 314 315 316
      // Allow the condition to validate the form.
      $condition = $form_state->get(['conditions', $condition_id]);
      $condition_values = (new FormState())
        ->setValues($values);
      $condition->validateConfigurationForm($form, $condition_values);
      // Update the original form values.
      $form_state->setValue(['visibility', $condition_id], $condition_values->getValues());
    }
317 318 319
  }

  /**
320
   * {@inheritdoc}
321
   */
322 323
  public function submitForm(array &$form, FormStateInterface $form_state) {
    parent::submitForm($form, $form_state);
324

325
    $entity = $this->entity;
326 327
    // The Block Entity form puts all block plugin form elements in the
    // settings form element, so just pass that to the block for submission.
328
    // @todo Find a way to avoid this manipulation.
329
    $settings = (new FormState())->setValues($form_state->getValue('settings'));
330

331
    // Call the plugin submit handler.
332
    $entity->getPlugin()->submitConfigurationForm($form, $settings);
333 334 335 336 337 338
    $block = $entity->getPlugin();
    // If this block is context-aware, set the context mapping.
    if ($block instanceof ContextAwarePluginInterface && $block->getContextDefinitions()) {
      $context_mapping = $settings->getValue('context_mapping', []);
      $block->setContextMapping($context_mapping);
    }
339
    // Update the original form values.
340
    $form_state->setValue('settings', $settings->getValues());
341

342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359
    // Submit visibility condition settings.
    foreach ($form_state->getValue('visibility') as $condition_id => $values) {
      // Allow the condition to submit the form.
      $condition = $form_state->get(['conditions', $condition_id]);
      $condition_values = (new FormState())
        ->setValues($values);
      $condition->submitConfigurationForm($form, $condition_values);
      if ($condition instanceof ContextAwarePluginInterface) {
        $context_mapping = isset($values['context_mapping']) ? $values['context_mapping'] : [];
        $condition->setContextMapping($context_mapping);
      }
      // Update the original form values.
      $condition_configuration = $condition->getConfiguration();
      $form_state->setValue(['visibility', $condition_id], $condition_configuration);
      // Update the visibility conditions on the block.
      $entity->getVisibilityConditions()->addInstanceId($condition_id, $condition_configuration);
    }

360 361
    // Save the settings of the plugin.
    $entity->save();
362

363
    drupal_set_message($this->t('The block configuration has been saved.'));
364 365 366
    $form_state->setRedirect(
      'block.admin_display_theme',
      array(
367
        'theme' => $form_state->getValue('theme'),
368
      ),
369
      array('query' => array('block-placement' => Html::getClass($this->entity->id())))
370
    );
371 372
  }

373 374 375 376 377 378 379 380 381 382 383 384 385
  /**
   * Generates a unique machine name for a block.
   *
   * @param \Drupal\block\BlockInterface $block
   *   The block entity.
   *
   * @return string
   *   Returns the unique name.
   */
  public function getUniqueMachineName(BlockInterface $block) {
    $suggestion = $block->getPlugin()->getMachineNameSuggestion();

    // Get all the blocks which starts with the suggested machine name.
386
    $query = $this->storage->getQuery();
387 388 389 390 391 392 393 394 395
    $query->condition('id', $suggestion, 'CONTAINS');
    $block_ids = $query->execute();

    $block_ids = array_map(function ($block_id) {
      $parts = explode('.', $block_id);
      return end($parts);
    }, $block_ids);

    // Iterate through potential IDs until we get a new one. E.g.
396
    // 'plugin', 'plugin_2', 'plugin_3', etc.
397 398 399 400 401 402 403 404
    $count = 1;
    $machine_default = $suggestion;
    while (in_array($machine_default, $block_ids)) {
      $machine_default = $suggestion . '_' . ++$count;
    }
    return $machine_default;
  }

405
}