Code Coverage |
||||||||||||||||
Lines |
Branches |
Paths |
Functions and Methods |
Classes and Traits |
||||||||||||
| Total | |
0.00% |
0 / 88 |
|
0.00% |
0 / 55 |
|
0.00% |
0 / 73 |
|
0.00% |
0 / 15 |
CRAP | |
0.00% |
0 / 1 |
| ContextualFormPanel | |
0.00% |
0 / 82 |
|
0.00% |
0 / 55 |
|
0.00% |
0 / 73 |
|
0.00% |
0 / 15 |
1482 | |
0.00% |
0 / 1 |
| create | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| label | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| buildForm | |
0.00% |
0 / 10 |
|
0.00% |
0 / 13 |
|
0.00% |
0 / 25 |
|
0.00% |
0 / 1 |
56 | |||
| build | |
0.00% |
0 / 17 |
|
0.00% |
0 / 3 |
|
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
| onAttachToRoot | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| onAttachToSlot | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| onActive | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| onUpdate | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| onDelete | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| isApplicable | |
0.00% |
0 / 1 |
|
0.00% |
0 / 3 |
|
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
| alterFormValues | |
0.00% |
0 / 9 |
|
0.00% |
0 / 11 |
|
0.00% |
0 / 25 |
|
0.00% |
0 / 1 |
72 | |||
| alterFormForComponent | |
0.00% |
0 / 13 |
|
0.00% |
0 / 5 |
|
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
12 | |||
| getComponentMetadata | |
0.00% |
0 / 9 |
|
0.00% |
0 / 3 |
|
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
| isMultipleItemsSlotSource | |
0.00% |
0 / 8 |
|
0.00% |
0 / 9 |
|
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
42 | |||
| removeItemSelector | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Drupal\display_builder\Plugin\display_builder\Island; |
| 6 | |
| 7 | use Drupal\Core\Form\FormStateInterface; |
| 8 | use Drupal\Core\StringTranslation\TranslatableMarkup; |
| 9 | use Drupal\display_builder\Attribute\Island; |
| 10 | use Drupal\display_builder\InstanceInterface; |
| 11 | use Drupal\display_builder\IslandPluginBase; |
| 12 | use Drupal\display_builder\IslandType; |
| 13 | use Drupal\display_builder\IslandWithFormInterface; |
| 14 | use Drupal\display_builder\IslandWithFormTrait; |
| 15 | use Drupal\ui_patterns\PropTypePluginManager; |
| 16 | use Drupal\ui_patterns\SourcePluginManager; |
| 17 | use Symfony\Component\DependencyInjection\ContainerInterface; |
| 18 | |
| 19 | /** |
| 20 | * Instance form island plugin implementation. |
| 21 | */ |
| 22 | #[Island( |
| 23 | id: 'contextual_form', |
| 24 | enabled_by_default: TRUE, |
| 25 | label: new TranslatableMarkup('Contextual form'), |
| 26 | description: new TranslatableMarkup('Configure the active component or block.'), |
| 27 | type: IslandType::Contextual, |
| 28 | )] |
| 29 | class ContextualFormPanel extends IslandPluginBase implements IslandWithFormInterface { |
| 30 | |
| 31 | use IslandWithFormTrait; |
| 32 | |
| 33 | /** |
| 34 | * The prop type plugin manager. |
| 35 | */ |
| 36 | protected PropTypePluginManager $propTypeManager; |
| 37 | |
| 38 | /** |
| 39 | * The UI Patterns source plugin manager. |
| 40 | */ |
| 41 | protected SourcePluginManager $sourceManager; |
| 42 | |
| 43 | /** |
| 44 | * {@inheritdoc} |
| 45 | */ |
| 46 | public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static { |
| 47 | $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); |
| 48 | $instance->propTypeManager = $container->get('plugin.manager.ui_patterns_prop_type'); |
| 49 | $instance->sourceManager = $container->get('plugin.manager.ui_patterns_source'); |
| 50 | |
| 51 | return $instance; |
| 52 | } |
| 53 | |
| 54 | /** |
| 55 | * {@inheritdoc} |
| 56 | */ |
| 57 | public function label(): string { |
| 58 | return 'Config'; |
| 59 | } |
| 60 | |
| 61 | /** |
| 62 | * {@inheritdoc} |
| 63 | */ |
| 64 | public function buildForm(array &$form, FormStateInterface $form_state): void { |
| 65 | try { |
| 66 | $contexts = $form_state->getBuildInfo()['args'][1] ?? []; |
| 67 | |
| 68 | $this->alterFormValues($form_state); |
| 69 | $source = $this->sourceManager->getSource($this->data['node_id'], [], $this->data, $contexts); |
| 70 | $form = $source ? $source->settingsForm([], $form_state) : []; |
| 71 | |
| 72 | if ($this->isMultipleItemsSlotSource($this->data['source'])) { |
| 73 | $form = $this->removeItemSelector($form); |
| 74 | } |
| 75 | |
| 76 | $component_id = ($this->data['source_id'] === 'component') ? $this->data['source']['component']['component_id'] ?? NULL : NULL; |
| 77 | |
| 78 | if ($component_id && ($this->data['source_id'] === 'component')) { |
| 79 | $this->alterFormForComponent($form, $component_id); |
| 80 | } |
| 81 | } |
| 82 | catch (\Exception) { |
| 83 | } |
| 84 | } |
| 85 | |
| 86 | /** |
| 87 | * {@inheritdoc} |
| 88 | */ |
| 89 | public function build(InstanceInterface $builder, array $data = [], array $options = []): array { |
| 90 | $build = parent::build($builder, $data, $options); |
| 91 | |
| 92 | if (empty($build)) { |
| 93 | return $build; |
| 94 | } |
| 95 | |
| 96 | $build = [ |
| 97 | 'source' => $build, |
| 98 | ]; |
| 99 | |
| 100 | $build['update'] = [ |
| 101 | '#type' => 'button', |
| 102 | '#value' => 'Update', |
| 103 | '#submit_button' => FALSE, |
| 104 | '#attributes' => [ |
| 105 | 'type' => 'button', |
| 106 | 'data-wysiwyg-fix' => TRUE, |
| 107 | ], |
| 108 | ]; |
| 109 | |
| 110 | $build = $this->htmxEvents->onInstanceFormChange($build, $this->builderId, $this->getPluginId(), $this->data['node_id']); |
| 111 | |
| 112 | return $this->htmxEvents->onInstanceUpdateButtonClick($build, $this->builderId, $this->getPluginId(), $this->data['node_id']); |
| 113 | } |
| 114 | |
| 115 | /** |
| 116 | * {@inheritdoc} |
| 117 | */ |
| 118 | public function onAttachToRoot(string $builder_id, string $instance_id): array { |
| 119 | return $this->reloadWithInstanceData($builder_id, $instance_id); |
| 120 | } |
| 121 | |
| 122 | /** |
| 123 | * {@inheritdoc} |
| 124 | */ |
| 125 | public function onAttachToSlot(string $builder_id, string $instance_id, string $parent_id): array { |
| 126 | return $this->reloadWithInstanceData($builder_id, $instance_id); |
| 127 | } |
| 128 | |
| 129 | /** |
| 130 | * {@inheritdoc} |
| 131 | */ |
| 132 | public function onActive(string $builder_id, array $data): array { |
| 133 | return $this->reloadWithLocalData($builder_id, $data); |
| 134 | } |
| 135 | |
| 136 | /** |
| 137 | * {@inheritdoc} |
| 138 | */ |
| 139 | public function onUpdate(string $builder_id, string $instance_id): array { |
| 140 | // Reload the form itself on update. |
| 141 | // @todo pass \Drupal\display_builder\InstanceInterface object in |
| 142 | // parameters instead of loading again. |
| 143 | /** @var \Drupal\display_builder\InstanceInterface $builder */ |
| 144 | $builder = $this->entityTypeManager->getStorage('display_builder_instance')->load($builder_id); |
| 145 | $data = $builder->get($instance_id); |
| 146 | |
| 147 | return $this->reloadWithLocalData($builder_id, $data); |
| 148 | } |
| 149 | |
| 150 | /** |
| 151 | * {@inheritdoc} |
| 152 | */ |
| 153 | public function onDelete(string $builder_id, string $parent_id): array { |
| 154 | return $this->reloadWithLocalData($builder_id, []); |
| 155 | } |
| 156 | |
| 157 | /** |
| 158 | * {@inheritdoc} |
| 159 | */ |
| 160 | public function isApplicable(): bool { |
| 161 | return isset($this->data['source_id']) && isset($this->data['node_id']); |
| 162 | } |
| 163 | |
| 164 | /** |
| 165 | * Alter the form values. |
| 166 | * |
| 167 | * @param \Drupal\Core\Form\FormStateInterface $form_state |
| 168 | * The form state. |
| 169 | */ |
| 170 | protected function alterFormValues(FormStateInterface $form_state): void { |
| 171 | // When this is an Ajax CALL, we directly inject the data into the source |
| 172 | // settings, but not during the rebuilt. |
| 173 | $values = $form_state->getValues(); |
| 174 | |
| 175 | if (isset($values['_drupal_ajax']) && $values['_drupal_ajax'] && !$form_state->isRebuilding()) { |
| 176 | if ($this->data['source_id'] !== 'component') { |
| 177 | $this->data['source'] = $values; |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | // When rebuilding the form, we need to inject the values into the source |
| 182 | // settings. |
| 183 | if ($form_state->isRebuilding()) { |
| 184 | // Allow to get the posted values through ajax, and give them to the |
| 185 | // source plugin through its settings (essential). |
| 186 | if (isset($values['source'])) { |
| 187 | unset($values['source']); |
| 188 | } |
| 189 | |
| 190 | if (!empty($values)) { |
| 191 | $this->data['source'] = $values; |
| 192 | } |
| 193 | } |
| 194 | } |
| 195 | |
| 196 | /** |
| 197 | * Alter the form in a case of a component. |
| 198 | * |
| 199 | * @param array $form |
| 200 | * The form. |
| 201 | * @param string|null $component_id |
| 202 | * The component ID. |
| 203 | */ |
| 204 | protected function alterFormForComponent(array &$form, ?string $component_id): void { |
| 205 | if (!$component_id) { |
| 206 | return; |
| 207 | } |
| 208 | |
| 209 | if (!isset($form['component']['component_id'])) { |
| 210 | $form['component']['component_id'] = [ |
| 211 | '#type' => 'hidden', |
| 212 | '#value' => $component_id, |
| 213 | ]; |
| 214 | } |
| 215 | $form['component']['#render_slots'] = FALSE; |
| 216 | $form['component']['#component_id'] = $component_id; |
| 217 | |
| 218 | $form = \array_merge( |
| 219 | ['info' => $this->getComponentMetadata($component_id)], |
| 220 | $form |
| 221 | ); |
| 222 | } |
| 223 | |
| 224 | /** |
| 225 | * Get component metadata. |
| 226 | * |
| 227 | * @param string $component_id |
| 228 | * The component ID. |
| 229 | * |
| 230 | * @return array |
| 231 | * A renderable array. |
| 232 | */ |
| 233 | protected function getComponentMetadata(string $component_id): array { |
| 234 | $component = $this->sdcManager->find($component_id); |
| 235 | $build = []; |
| 236 | |
| 237 | if ($description = $component->metadata->description) { |
| 238 | $build[] = [ |
| 239 | '#type' => 'html_tag', |
| 240 | '#tag' => 'p', |
| 241 | '#value' => $description, |
| 242 | ]; |
| 243 | } |
| 244 | |
| 245 | return $build; |
| 246 | } |
| 247 | |
| 248 | /** |
| 249 | * Has the slot source multiple items? |
| 250 | * |
| 251 | * Some slot sources have 'multiple' items, with a select form element first, |
| 252 | * then an item specific form changing with Ajax. They have both a plugin_id |
| 253 | * key and a dynamic key with the value of the plugin_id. |
| 254 | * |
| 255 | * @param array $data |
| 256 | * The slot source data containing: |
| 257 | * - plugin_id: The plugin ID. |
| 258 | * |
| 259 | * @return bool |
| 260 | * Is multiple or not. |
| 261 | */ |
| 262 | private function isMultipleItemsSlotSource(array $data): bool { |
| 263 | if (!isset($data['plugin_id']) || !\is_string($data['plugin_id'])) { |
| 264 | return FALSE; |
| 265 | } |
| 266 | |
| 267 | if (\count($data) === 1) { |
| 268 | // If there is only plugin_id, without any settings, it is OK. |
| 269 | return TRUE; |
| 270 | } |
| 271 | // If there are settings, we need at least the one specific to the item. |
| 272 | $plugin_id = (string) $data['plugin_id']; |
| 273 | |
| 274 | if (isset($data[$plugin_id]) && \is_array($data[$plugin_id])) { |
| 275 | return TRUE; |
| 276 | } |
| 277 | |
| 278 | return FALSE; |
| 279 | } |
| 280 | |
| 281 | /** |
| 282 | * Remove the item selector from a form. |
| 283 | * |
| 284 | * For multiple items slot sources, we don't want to show the item selector |
| 285 | * since it is already selected in the slot configuration. |
| 286 | * |
| 287 | * @param array $form |
| 288 | * The form array. |
| 289 | * |
| 290 | * @return array |
| 291 | * The modified form array. |
| 292 | */ |
| 293 | private function removeItemSelector(array $form): array { |
| 294 | $form['plugin_id']['#type'] = 'hidden'; |
| 295 | unset($form['plugin_id']['#options']); |
| 296 | |
| 297 | return $form; |
| 298 | } |
| 299 | |
| 300 | } |
Below are the source code lines that represent each code branch as identified by Xdebug. Please note a branch is not
necessarily coterminous with a line, a line may contain multiple branches and therefore show up more than once.
Please also be aware that some branches may be implicit rather than explicit, e.g. an if statement
always has an else as part of its logical flow even if you didn't write one.
| 204 | protected function alterFormForComponent(array &$form, ?string $component_id): void { |
| 205 | if (!$component_id) { |
| 206 | return; |
| 209 | if (!isset($form['component']['component_id'])) { |
| 211 | '#type' => 'hidden', |
| 212 | '#value' => $component_id, |
| 213 | ]; |
| 214 | } |
| 215 | $form['component']['#render_slots'] = FALSE; |
| 215 | $form['component']['#render_slots'] = FALSE; |
| 216 | $form['component']['#component_id'] = $component_id; |
| 217 | |
| 218 | $form = \array_merge( |
| 219 | ['info' => $this->getComponentMetadata($component_id)], |
| 220 | $form |
| 221 | ); |
| 222 | } |
| 170 | protected function alterFormValues(FormStateInterface $form_state): void { |
| 171 | // When this is an Ajax CALL, we directly inject the data into the source |
| 172 | // settings, but not during the rebuilt. |
| 173 | $values = $form_state->getValues(); |
| 174 | |
| 175 | if (isset($values['_drupal_ajax']) && $values['_drupal_ajax'] && !$form_state->isRebuilding()) { |
| 175 | if (isset($values['_drupal_ajax']) && $values['_drupal_ajax'] && !$form_state->isRebuilding()) { |
| 175 | if (isset($values['_drupal_ajax']) && $values['_drupal_ajax'] && !$form_state->isRebuilding()) { |
| 176 | if ($this->data['source_id'] !== 'component') { |
| 177 | $this->data['source'] = $values; |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | // When rebuilding the form, we need to inject the values into the source |
| 182 | // settings. |
| 183 | if ($form_state->isRebuilding()) { |
| 183 | if ($form_state->isRebuilding()) { |
| 186 | if (isset($values['source'])) { |
| 187 | unset($values['source']); |
| 188 | } |
| 189 | |
| 190 | if (!empty($values)) { |
| 190 | if (!empty($values)) { |
| 191 | $this->data['source'] = $values; |
| 192 | } |
| 193 | } |
| 194 | } |
| 194 | } |
| 89 | public function build(InstanceInterface $builder, array $data = [], array $options = []): array { |
| 90 | $build = parent::build($builder, $data, $options); |
| 91 | |
| 92 | if (empty($build)) { |
| 93 | return $build; |
| 97 | 'source' => $build, |
| 98 | ]; |
| 99 | |
| 100 | $build['update'] = [ |
| 101 | '#type' => 'button', |
| 102 | '#value' => 'Update', |
| 103 | '#submit_button' => FALSE, |
| 104 | '#attributes' => [ |
| 105 | 'type' => 'button', |
| 106 | 'data-wysiwyg-fix' => TRUE, |
| 107 | ], |
| 108 | ]; |
| 109 | |
| 110 | $build = $this->htmxEvents->onInstanceFormChange($build, $this->builderId, $this->getPluginId(), $this->data['node_id']); |
| 111 | |
| 112 | return $this->htmxEvents->onInstanceUpdateButtonClick($build, $this->builderId, $this->getPluginId(), $this->data['node_id']); |
| 64 | public function buildForm(array &$form, FormStateInterface $form_state): void { |
| 65 | try { |
| 66 | $contexts = $form_state->getBuildInfo()['args'][1] ?? []; |
| 67 | |
| 68 | $this->alterFormValues($form_state); |
| 69 | $source = $this->sourceManager->getSource($this->data['node_id'], [], $this->data, $contexts); |
| 70 | $form = $source ? $source->settingsForm([], $form_state) : []; |
| 70 | $form = $source ? $source->settingsForm([], $form_state) : []; |
| 70 | $form = $source ? $source->settingsForm([], $form_state) : []; |
| 70 | $form = $source ? $source->settingsForm([], $form_state) : []; |
| 71 | |
| 72 | if ($this->isMultipleItemsSlotSource($this->data['source'])) { |
| 73 | $form = $this->removeItemSelector($form); |
| 74 | } |
| 75 | |
| 76 | $component_id = ($this->data['source_id'] === 'component') ? $this->data['source']['component']['component_id'] ?? NULL : NULL; |
| 76 | $component_id = ($this->data['source_id'] === 'component') ? $this->data['source']['component']['component_id'] ?? NULL : NULL; |
| 76 | $component_id = ($this->data['source_id'] === 'component') ? $this->data['source']['component']['component_id'] ?? NULL : NULL; |
| 76 | $component_id = ($this->data['source_id'] === 'component') ? $this->data['source']['component']['component_id'] ?? NULL : NULL; |
| 76 | $component_id = ($this->data['source_id'] === 'component') ? $this->data['source']['component']['component_id'] ?? NULL : NULL; |
| 77 | |
| 78 | if ($component_id && ($this->data['source_id'] === 'component')) { |
| 78 | if ($component_id && ($this->data['source_id'] === 'component')) { |
| 79 | $this->alterFormForComponent($form, $component_id); |
| 82 | catch (\Exception) { |
| 84 | } |
| 46 | public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static { |
| 47 | $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); |
| 48 | $instance->propTypeManager = $container->get('plugin.manager.ui_patterns_prop_type'); |
| 49 | $instance->sourceManager = $container->get('plugin.manager.ui_patterns_source'); |
| 50 | |
| 51 | return $instance; |
| 233 | protected function getComponentMetadata(string $component_id): array { |
| 234 | $component = $this->sdcManager->find($component_id); |
| 235 | $build = []; |
| 236 | |
| 237 | if ($description = $component->metadata->description) { |
| 239 | '#type' => 'html_tag', |
| 240 | '#tag' => 'p', |
| 241 | '#value' => $description, |
| 242 | ]; |
| 243 | } |
| 244 | |
| 245 | return $build; |
| 245 | return $build; |
| 161 | return isset($this->data['source_id']) && isset($this->data['node_id']); |
| 161 | return isset($this->data['source_id']) && isset($this->data['node_id']); |
| 161 | return isset($this->data['source_id']) && isset($this->data['node_id']); |
| 262 | private function isMultipleItemsSlotSource(array $data): bool { |
| 263 | if (!isset($data['plugin_id']) || !\is_string($data['plugin_id'])) { |
| 263 | if (!isset($data['plugin_id']) || !\is_string($data['plugin_id'])) { |
| 264 | return FALSE; |
| 267 | if (\count($data) === 1) { |
| 269 | return TRUE; |
| 272 | $plugin_id = (string) $data['plugin_id']; |
| 273 | |
| 274 | if (isset($data[$plugin_id]) && \is_array($data[$plugin_id])) { |
| 274 | if (isset($data[$plugin_id]) && \is_array($data[$plugin_id])) { |
| 275 | return TRUE; |
| 278 | return FALSE; |
| 58 | return 'Config'; |
| 132 | public function onActive(string $builder_id, array $data): array { |
| 133 | return $this->reloadWithLocalData($builder_id, $data); |
| 118 | public function onAttachToRoot(string $builder_id, string $instance_id): array { |
| 119 | return $this->reloadWithInstanceData($builder_id, $instance_id); |
| 125 | public function onAttachToSlot(string $builder_id, string $instance_id, string $parent_id): array { |
| 126 | return $this->reloadWithInstanceData($builder_id, $instance_id); |
| 153 | public function onDelete(string $builder_id, string $parent_id): array { |
| 154 | return $this->reloadWithLocalData($builder_id, []); |
| 139 | public function onUpdate(string $builder_id, string $instance_id): array { |
| 140 | // Reload the form itself on update. |
| 141 | // @todo pass \Drupal\display_builder\InstanceInterface object in |
| 142 | // parameters instead of loading again. |
| 143 | /** @var \Drupal\display_builder\InstanceInterface $builder */ |
| 144 | $builder = $this->entityTypeManager->getStorage('display_builder_instance')->load($builder_id); |
| 145 | $data = $builder->get($instance_id); |
| 146 | |
| 147 | return $this->reloadWithLocalData($builder_id, $data); |
| 293 | private function removeItemSelector(array $form): array { |
| 294 | $form['plugin_id']['#type'] = 'hidden'; |
| 295 | unset($form['plugin_id']['#options']); |
| 296 | |
| 297 | return $form; |