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