annotate core/modules/block/src/BlockForm.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\block;
Chris@0 4
Chris@0 5 use Drupal\Component\Utility\Html;
Chris@0 6 use Drupal\Core\Plugin\PluginFormFactoryInterface;
Chris@0 7 use Drupal\Core\Block\BlockPluginInterface;
Chris@0 8 use Drupal\Core\Entity\EntityForm;
Chris@0 9 use Drupal\Core\Entity\EntityManagerInterface;
Chris@0 10 use Drupal\Core\Executable\ExecutableManagerInterface;
Chris@0 11 use Drupal\Core\Extension\ThemeHandlerInterface;
Chris@0 12 use Drupal\Core\Form\FormStateInterface;
Chris@0 13 use Drupal\Core\Form\SubformState;
Chris@0 14 use Drupal\Core\Language\LanguageManagerInterface;
Chris@0 15 use Drupal\Core\Plugin\ContextAwarePluginInterface;
Chris@0 16 use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
Chris@0 17 use Drupal\Core\Plugin\PluginWithFormsInterface;
Chris@0 18 use Symfony\Component\DependencyInjection\ContainerInterface;
Chris@0 19
Chris@0 20 /**
Chris@0 21 * Provides form for block instance forms.
Chris@14 22 *
Chris@14 23 * @internal
Chris@0 24 */
Chris@0 25 class BlockForm extends EntityForm {
Chris@0 26
Chris@0 27 /**
Chris@0 28 * The block entity.
Chris@0 29 *
Chris@0 30 * @var \Drupal\block\BlockInterface
Chris@0 31 */
Chris@0 32 protected $entity;
Chris@0 33
Chris@0 34 /**
Chris@0 35 * The block storage.
Chris@0 36 *
Chris@0 37 * @var \Drupal\Core\Entity\EntityStorageInterface
Chris@0 38 */
Chris@0 39 protected $storage;
Chris@0 40
Chris@0 41 /**
Chris@0 42 * The condition plugin manager.
Chris@0 43 *
Chris@0 44 * @var \Drupal\Core\Condition\ConditionManager
Chris@0 45 */
Chris@0 46 protected $manager;
Chris@0 47
Chris@0 48 /**
Chris@0 49 * The language manager service.
Chris@0 50 *
Chris@0 51 * @var \Drupal\Core\Language\LanguageManagerInterface
Chris@0 52 */
Chris@0 53 protected $language;
Chris@0 54
Chris@0 55 /**
Chris@0 56 * The theme handler.
Chris@0 57 *
Chris@0 58 * @var \Drupal\Core\Extension\ThemeHandler
Chris@0 59 */
Chris@0 60 protected $themeHandler;
Chris@0 61
Chris@0 62 /**
Chris@0 63 * The context repository service.
Chris@0 64 *
Chris@0 65 * @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface
Chris@0 66 */
Chris@0 67 protected $contextRepository;
Chris@0 68
Chris@0 69 /**
Chris@0 70 * The plugin form manager.
Chris@0 71 *
Chris@0 72 * @var \Drupal\Core\Plugin\PluginFormFactoryInterface
Chris@0 73 */
Chris@0 74 protected $pluginFormFactory;
Chris@0 75
Chris@0 76 /**
Chris@0 77 * Constructs a BlockForm object.
Chris@0 78 *
Chris@0 79 * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
Chris@0 80 * The entity manager.
Chris@0 81 * @param \Drupal\Core\Executable\ExecutableManagerInterface $manager
Chris@0 82 * The ConditionManager for building the visibility UI.
Chris@0 83 * @param \Drupal\Core\Plugin\Context\ContextRepositoryInterface $context_repository
Chris@0 84 * The lazy context repository service.
Chris@0 85 * @param \Drupal\Core\Language\LanguageManagerInterface $language
Chris@0 86 * The language manager.
Chris@0 87 * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
Chris@0 88 * The theme handler.
Chris@0 89 * @param \Drupal\Core\Plugin\PluginFormFactoryInterface $plugin_form_manager
Chris@0 90 * The plugin form manager.
Chris@0 91 */
Chris@0 92 public function __construct(EntityManagerInterface $entity_manager, ExecutableManagerInterface $manager, ContextRepositoryInterface $context_repository, LanguageManagerInterface $language, ThemeHandlerInterface $theme_handler, PluginFormFactoryInterface $plugin_form_manager) {
Chris@0 93 $this->storage = $entity_manager->getStorage('block');
Chris@0 94 $this->manager = $manager;
Chris@0 95 $this->contextRepository = $context_repository;
Chris@0 96 $this->language = $language;
Chris@0 97 $this->themeHandler = $theme_handler;
Chris@0 98 $this->pluginFormFactory = $plugin_form_manager;
Chris@0 99 }
Chris@0 100
Chris@0 101 /**
Chris@0 102 * {@inheritdoc}
Chris@0 103 */
Chris@0 104 public static function create(ContainerInterface $container) {
Chris@0 105 return new static(
Chris@0 106 $container->get('entity.manager'),
Chris@0 107 $container->get('plugin.manager.condition'),
Chris@0 108 $container->get('context.repository'),
Chris@0 109 $container->get('language_manager'),
Chris@0 110 $container->get('theme_handler'),
Chris@0 111 $container->get('plugin_form.factory')
Chris@0 112 );
Chris@0 113 }
Chris@0 114
Chris@0 115 /**
Chris@0 116 * {@inheritdoc}
Chris@0 117 */
Chris@0 118 public function form(array $form, FormStateInterface $form_state) {
Chris@0 119 $entity = $this->entity;
Chris@0 120
Chris@0 121 // Store theme settings in $form_state for use below.
Chris@0 122 if (!$theme = $entity->getTheme()) {
Chris@0 123 $theme = $this->config('system.theme')->get('default');
Chris@0 124 }
Chris@0 125 $form_state->set('block_theme', $theme);
Chris@0 126
Chris@0 127 // Store the gathered contexts in the form state for other objects to use
Chris@0 128 // during form building.
Chris@0 129 $form_state->setTemporaryValue('gathered_contexts', $this->contextRepository->getAvailableContexts());
Chris@0 130
Chris@0 131 $form['#tree'] = TRUE;
Chris@0 132 $form['settings'] = [];
Chris@0 133 $subform_state = SubformState::createForSubform($form['settings'], $form, $form_state);
Chris@0 134 $form['settings'] = $this->getPluginForm($entity->getPlugin())->buildConfigurationForm($form['settings'], $subform_state);
Chris@0 135 $form['visibility'] = $this->buildVisibilityInterface([], $form_state);
Chris@0 136
Chris@0 137 // If creating a new block, calculate a safe default machine name.
Chris@0 138 $form['id'] = [
Chris@0 139 '#type' => 'machine_name',
Chris@0 140 '#maxlength' => 64,
Chris@0 141 '#description' => $this->t('A unique name for this block instance. Must be alpha-numeric and underscore separated.'),
Chris@0 142 '#default_value' => !$entity->isNew() ? $entity->id() : $this->getUniqueMachineName($entity),
Chris@0 143 '#machine_name' => [
Chris@0 144 'exists' => '\Drupal\block\Entity\Block::load',
Chris@0 145 'replace_pattern' => '[^a-z0-9_.]+',
Chris@0 146 'source' => ['settings', 'label'],
Chris@0 147 ],
Chris@0 148 '#required' => TRUE,
Chris@0 149 '#disabled' => !$entity->isNew(),
Chris@0 150 ];
Chris@0 151
Chris@0 152 // Theme settings.
Chris@0 153 if ($entity->getTheme()) {
Chris@0 154 $form['theme'] = [
Chris@0 155 '#type' => 'value',
Chris@0 156 '#value' => $theme,
Chris@0 157 ];
Chris@0 158 }
Chris@0 159 else {
Chris@0 160 $theme_options = [];
Chris@0 161 foreach ($this->themeHandler->listInfo() as $theme_name => $theme_info) {
Chris@0 162 if (!empty($theme_info->status)) {
Chris@0 163 $theme_options[$theme_name] = $theme_info->info['name'];
Chris@0 164 }
Chris@0 165 }
Chris@0 166 $form['theme'] = [
Chris@0 167 '#type' => 'select',
Chris@0 168 '#options' => $theme_options,
Chris@0 169 '#title' => t('Theme'),
Chris@0 170 '#default_value' => $theme,
Chris@0 171 '#ajax' => [
Chris@0 172 'callback' => '::themeSwitch',
Chris@0 173 'wrapper' => 'edit-block-region-wrapper',
Chris@0 174 ],
Chris@0 175 ];
Chris@0 176 }
Chris@0 177
Chris@0 178 // Hidden weight setting.
Chris@0 179 $weight = $entity->isNew() ? $this->getRequest()->query->get('weight', 0) : $entity->getWeight();
Chris@0 180 $form['weight'] = [
Chris@0 181 '#type' => 'hidden',
Chris@0 182 '#default_value' => $weight,
Chris@0 183 ];
Chris@0 184
Chris@0 185 // Region settings.
Chris@0 186 $entity_region = $entity->getRegion();
Chris@0 187 $region = $entity->isNew() ? $this->getRequest()->query->get('region', $entity_region) : $entity_region;
Chris@0 188 $form['region'] = [
Chris@0 189 '#type' => 'select',
Chris@0 190 '#title' => $this->t('Region'),
Chris@0 191 '#description' => $this->t('Select the region where this block should be displayed.'),
Chris@0 192 '#default_value' => $region,
Chris@0 193 '#required' => TRUE,
Chris@0 194 '#options' => system_region_list($theme, REGIONS_VISIBLE),
Chris@0 195 '#prefix' => '<div id="edit-block-region-wrapper">',
Chris@0 196 '#suffix' => '</div>',
Chris@0 197 ];
Chris@0 198 $form['#attached']['library'][] = 'block/drupal.block.admin';
Chris@0 199 return $form;
Chris@0 200 }
Chris@0 201
Chris@0 202 /**
Chris@0 203 * Handles switching the available regions based on the selected theme.
Chris@0 204 */
Chris@0 205 public function themeSwitch($form, FormStateInterface $form_state) {
Chris@0 206 $form['region']['#options'] = system_region_list($form_state->getValue('theme'), REGIONS_VISIBLE);
Chris@0 207 return $form['region'];
Chris@0 208 }
Chris@0 209
Chris@0 210 /**
Chris@0 211 * Helper function for building the visibility UI form.
Chris@0 212 *
Chris@0 213 * @param array $form
Chris@0 214 * An associative array containing the structure of the form.
Chris@0 215 * @param \Drupal\Core\Form\FormStateInterface $form_state
Chris@0 216 * The current state of the form.
Chris@0 217 *
Chris@0 218 * @return array
Chris@0 219 * The form array with the visibility UI added in.
Chris@0 220 */
Chris@0 221 protected function buildVisibilityInterface(array $form, FormStateInterface $form_state) {
Chris@0 222 $form['visibility_tabs'] = [
Chris@0 223 '#type' => 'vertical_tabs',
Chris@0 224 '#title' => $this->t('Visibility'),
Chris@0 225 '#parents' => ['visibility_tabs'],
Chris@0 226 '#attached' => [
Chris@0 227 'library' => [
Chris@0 228 'block/drupal.block',
Chris@0 229 ],
Chris@0 230 ],
Chris@0 231 ];
Chris@0 232 // @todo Allow list of conditions to be configured in
Chris@0 233 // https://www.drupal.org/node/2284687.
Chris@0 234 $visibility = $this->entity->getVisibility();
Chris@17 235 $definitions = $this->manager->getFilteredDefinitions('block_ui', $form_state->getTemporaryValue('gathered_contexts'), ['block' => $this->entity]);
Chris@17 236 foreach ($definitions as $condition_id => $definition) {
Chris@0 237 // Don't display the current theme condition.
Chris@0 238 if ($condition_id == 'current_theme') {
Chris@0 239 continue;
Chris@0 240 }
Chris@0 241 // Don't display the language condition until we have multiple languages.
Chris@0 242 if ($condition_id == 'language' && !$this->language->isMultilingual()) {
Chris@0 243 continue;
Chris@0 244 }
Chris@0 245 /** @var \Drupal\Core\Condition\ConditionInterface $condition */
Chris@0 246 $condition = $this->manager->createInstance($condition_id, isset($visibility[$condition_id]) ? $visibility[$condition_id] : []);
Chris@0 247 $form_state->set(['conditions', $condition_id], $condition);
Chris@0 248 $condition_form = $condition->buildConfigurationForm([], $form_state);
Chris@0 249 $condition_form['#type'] = 'details';
Chris@0 250 $condition_form['#title'] = $condition->getPluginDefinition()['label'];
Chris@0 251 $condition_form['#group'] = 'visibility_tabs';
Chris@0 252 $form[$condition_id] = $condition_form;
Chris@0 253 }
Chris@0 254
Chris@0 255 if (isset($form['node_type'])) {
Chris@0 256 $form['node_type']['#title'] = $this->t('Content types');
Chris@0 257 $form['node_type']['bundles']['#title'] = $this->t('Content types');
Chris@0 258 $form['node_type']['negate']['#type'] = 'value';
Chris@0 259 $form['node_type']['negate']['#title_display'] = 'invisible';
Chris@0 260 $form['node_type']['negate']['#value'] = $form['node_type']['negate']['#default_value'];
Chris@0 261 }
Chris@0 262 if (isset($form['user_role'])) {
Chris@0 263 $form['user_role']['#title'] = $this->t('Roles');
Chris@0 264 unset($form['user_role']['roles']['#description']);
Chris@0 265 $form['user_role']['negate']['#type'] = 'value';
Chris@0 266 $form['user_role']['negate']['#value'] = $form['user_role']['negate']['#default_value'];
Chris@0 267 }
Chris@0 268 if (isset($form['request_path'])) {
Chris@0 269 $form['request_path']['#title'] = $this->t('Pages');
Chris@0 270 $form['request_path']['negate']['#type'] = 'radios';
Chris@0 271 $form['request_path']['negate']['#default_value'] = (int) $form['request_path']['negate']['#default_value'];
Chris@0 272 $form['request_path']['negate']['#title_display'] = 'invisible';
Chris@0 273 $form['request_path']['negate']['#options'] = [
Chris@0 274 $this->t('Show for the listed pages'),
Chris@0 275 $this->t('Hide for the listed pages'),
Chris@0 276 ];
Chris@0 277 }
Chris@0 278 if (isset($form['language'])) {
Chris@0 279 $form['language']['negate']['#type'] = 'value';
Chris@0 280 $form['language']['negate']['#value'] = $form['language']['negate']['#default_value'];
Chris@0 281 }
Chris@0 282 return $form;
Chris@0 283 }
Chris@0 284
Chris@0 285 /**
Chris@0 286 * {@inheritdoc}
Chris@0 287 */
Chris@0 288 protected function actions(array $form, FormStateInterface $form_state) {
Chris@0 289 $actions = parent::actions($form, $form_state);
Chris@0 290 $actions['submit']['#value'] = $this->t('Save block');
Chris@0 291 $actions['delete']['#title'] = $this->t('Remove block');
Chris@0 292 return $actions;
Chris@0 293 }
Chris@0 294
Chris@0 295 /**
Chris@0 296 * {@inheritdoc}
Chris@0 297 */
Chris@0 298 public function validateForm(array &$form, FormStateInterface $form_state) {
Chris@0 299 parent::validateForm($form, $form_state);
Chris@0 300
Chris@0 301 $form_state->setValue('weight', (int) $form_state->getValue('weight'));
Chris@0 302 // The Block Entity form puts all block plugin form elements in the
Chris@0 303 // settings form element, so just pass that to the block for validation.
Chris@0 304 $this->getPluginForm($this->entity->getPlugin())->validateConfigurationForm($form['settings'], SubformState::createForSubform($form['settings'], $form, $form_state));
Chris@0 305 $this->validateVisibility($form, $form_state);
Chris@0 306 }
Chris@0 307
Chris@0 308 /**
Chris@0 309 * Helper function to independently validate the visibility UI.
Chris@0 310 *
Chris@0 311 * @param array $form
Chris@0 312 * A nested array form elements comprising the form.
Chris@0 313 * @param \Drupal\Core\Form\FormStateInterface $form_state
Chris@0 314 * The current state of the form.
Chris@0 315 */
Chris@0 316 protected function validateVisibility(array $form, FormStateInterface $form_state) {
Chris@0 317 // Validate visibility condition settings.
Chris@0 318 foreach ($form_state->getValue('visibility') as $condition_id => $values) {
Chris@0 319 // All condition plugins use 'negate' as a Boolean in their schema.
Chris@0 320 // However, certain form elements may return it as 0/1. Cast here to
Chris@0 321 // ensure the data is in the expected type.
Chris@0 322 if (array_key_exists('negate', $values)) {
Chris@0 323 $form_state->setValue(['visibility', $condition_id, 'negate'], (bool) $values['negate']);
Chris@0 324 }
Chris@0 325
Chris@0 326 // Allow the condition to validate the form.
Chris@0 327 $condition = $form_state->get(['conditions', $condition_id]);
Chris@0 328 $condition->validateConfigurationForm($form['visibility'][$condition_id], SubformState::createForSubform($form['visibility'][$condition_id], $form, $form_state));
Chris@0 329 }
Chris@0 330 }
Chris@0 331
Chris@0 332 /**
Chris@0 333 * {@inheritdoc}
Chris@0 334 */
Chris@0 335 public function submitForm(array &$form, FormStateInterface $form_state) {
Chris@0 336 parent::submitForm($form, $form_state);
Chris@0 337
Chris@0 338 $entity = $this->entity;
Chris@0 339 // The Block Entity form puts all block plugin form elements in the
Chris@0 340 // settings form element, so just pass that to the block for submission.
Chris@0 341 $sub_form_state = SubformState::createForSubform($form['settings'], $form, $form_state);
Chris@0 342 // Call the plugin submit handler.
Chris@0 343 $block = $entity->getPlugin();
Chris@0 344 $this->getPluginForm($block)->submitConfigurationForm($form, $sub_form_state);
Chris@0 345 // If this block is context-aware, set the context mapping.
Chris@0 346 if ($block instanceof ContextAwarePluginInterface && $block->getContextDefinitions()) {
Chris@0 347 $context_mapping = $sub_form_state->getValue('context_mapping', []);
Chris@0 348 $block->setContextMapping($context_mapping);
Chris@0 349 }
Chris@0 350
Chris@0 351 $this->submitVisibility($form, $form_state);
Chris@0 352
Chris@0 353 // Save the settings of the plugin.
Chris@0 354 $entity->save();
Chris@0 355
Chris@17 356 $this->messenger()->addStatus($this->t('The block configuration has been saved.'));
Chris@0 357 $form_state->setRedirect(
Chris@0 358 'block.admin_display_theme',
Chris@0 359 [
Chris@0 360 'theme' => $form_state->getValue('theme'),
Chris@0 361 ],
Chris@0 362 ['query' => ['block-placement' => Html::getClass($this->entity->id())]]
Chris@0 363 );
Chris@0 364 }
Chris@0 365
Chris@0 366 /**
Chris@0 367 * Helper function to independently submit the visibility UI.
Chris@0 368 *
Chris@0 369 * @param array $form
Chris@0 370 * A nested array form elements comprising the form.
Chris@0 371 * @param \Drupal\Core\Form\FormStateInterface $form_state
Chris@0 372 * The current state of the form.
Chris@0 373 */
Chris@0 374 protected function submitVisibility(array $form, FormStateInterface $form_state) {
Chris@0 375 foreach ($form_state->getValue('visibility') as $condition_id => $values) {
Chris@0 376 // Allow the condition to submit the form.
Chris@0 377 $condition = $form_state->get(['conditions', $condition_id]);
Chris@0 378 $condition->submitConfigurationForm($form['visibility'][$condition_id], SubformState::createForSubform($form['visibility'][$condition_id], $form, $form_state));
Chris@0 379
Chris@0 380 // Setting conditions' context mappings is the plugins' responsibility.
Chris@0 381 // This code exists for backwards compatibility, because
Chris@0 382 // \Drupal\Core\Condition\ConditionPluginBase::submitConfigurationForm()
Chris@0 383 // did not set its own mappings until Drupal 8.2
Chris@0 384 // @todo Remove the code that sets context mappings in Drupal 9.0.0.
Chris@0 385 if ($condition instanceof ContextAwarePluginInterface) {
Chris@0 386 $context_mapping = isset($values['context_mapping']) ? $values['context_mapping'] : [];
Chris@0 387 $condition->setContextMapping($context_mapping);
Chris@0 388 }
Chris@0 389
Chris@0 390 $condition_configuration = $condition->getConfiguration();
Chris@0 391 // Update the visibility conditions on the block.
Chris@0 392 $this->entity->getVisibilityConditions()->addInstanceId($condition_id, $condition_configuration);
Chris@0 393 }
Chris@0 394 }
Chris@0 395
Chris@0 396 /**
Chris@0 397 * Generates a unique machine name for a block.
Chris@0 398 *
Chris@0 399 * @param \Drupal\block\BlockInterface $block
Chris@0 400 * The block entity.
Chris@0 401 *
Chris@0 402 * @return string
Chris@0 403 * Returns the unique name.
Chris@0 404 */
Chris@0 405 public function getUniqueMachineName(BlockInterface $block) {
Chris@0 406 $suggestion = $block->getPlugin()->getMachineNameSuggestion();
Chris@0 407
Chris@0 408 // Get all the blocks which starts with the suggested machine name.
Chris@0 409 $query = $this->storage->getQuery();
Chris@0 410 $query->condition('id', $suggestion, 'CONTAINS');
Chris@0 411 $block_ids = $query->execute();
Chris@0 412
Chris@0 413 $block_ids = array_map(function ($block_id) {
Chris@0 414 $parts = explode('.', $block_id);
Chris@0 415 return end($parts);
Chris@0 416 }, $block_ids);
Chris@0 417
Chris@0 418 // Iterate through potential IDs until we get a new one. E.g.
Chris@0 419 // 'plugin', 'plugin_2', 'plugin_3', etc.
Chris@0 420 $count = 1;
Chris@0 421 $machine_default = $suggestion;
Chris@0 422 while (in_array($machine_default, $block_ids)) {
Chris@0 423 $machine_default = $suggestion . '_' . ++$count;
Chris@0 424 }
Chris@0 425 return $machine_default;
Chris@0 426 }
Chris@0 427
Chris@0 428 /**
Chris@0 429 * Retrieves the plugin form for a given block and operation.
Chris@0 430 *
Chris@0 431 * @param \Drupal\Core\Block\BlockPluginInterface $block
Chris@0 432 * The block plugin.
Chris@0 433 *
Chris@0 434 * @return \Drupal\Core\Plugin\PluginFormInterface
Chris@0 435 * The plugin form for the block.
Chris@0 436 */
Chris@0 437 protected function getPluginForm(BlockPluginInterface $block) {
Chris@0 438 if ($block instanceof PluginWithFormsInterface) {
Chris@0 439 return $this->pluginFormFactory->createInstance($block, 'configure');
Chris@0 440 }
Chris@0 441 return $block;
Chris@0 442 }
Chris@0 443
Chris@0 444 }