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