Code Coverage |
||||||||||||||||
Lines |
Branches |
Paths |
Functions and Methods |
Classes and Traits |
||||||||||||
| Total | |
32.75% |
94 / 287 |
|
39.18% |
38 / 97 |
|
16.22% |
12 / 74 |
|
40.00% |
6 / 15 |
CRAP | |
0.00% |
0 / 1 |
| ApiController | |
32.75% |
94 / 287 |
|
39.18% |
38 / 97 |
|
16.22% |
12 / 74 |
|
46.67% |
7 / 15 |
1580.75 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
1 / 1 |
n/a |
0 / 0 |
n/a |
0 / 0 |
|
100.00% |
1 / 1 |
1 | |||||
| reloadIsland | |
0.00% |
0 / 23 |
|
0.00% |
0 / 5 |
|
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
12 | |||
| attachToRoot | |
57.58% |
19 / 33 |
|
75.00% |
12 / 16 |
|
22.22% |
2 / 9 |
|
0.00% |
0 / 1 |
30.05 | |||
| attachToSlot | |
46.15% |
18 / 39 |
|
56.25% |
9 / 16 |
|
11.11% |
1 / 9 |
|
0.00% |
0 / 1 |
41.41 | |||
| get | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| update | |
21.57% |
11 / 51 |
|
16.67% |
2 / 12 |
|
14.29% |
1 / 7 |
|
0.00% |
0 / 1 |
28.67 | |||
| thirdPartySettingsUpdate | |
30.56% |
11 / 36 |
|
66.67% |
2 / 3 |
|
50.00% |
1 / 2 |
|
0.00% |
0 / 1 |
2.50 | |||
| undo | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| redo | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| clear | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| attachPresetToRoot | |
0.00% |
0 / 22 |
|
0.00% |
0 / 8 |
|
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
| attachPresetToSlot | |
0.00% |
0 / 24 |
|
0.00% |
0 / 8 |
|
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
| resolveSkipIslandId | |
100.00% |
5 / 5 |
|
75.00% |
6 / 8 |
|
40.00% |
2 / 5 |
|
100.00% |
1 / 1 |
7.46 | |||
| validateIslandForm | |
0.00% |
0 / 24 |
|
0.00% |
0 / 14 |
|
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
56 | |||
| responseMessageError | |
100.00% |
12 / 12 |
|
100.00% |
3 / 3 |
|
50.00% |
1 / 2 |
|
100.00% |
1 / 1 |
2.50 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Drupal\display_builder\Controller; |
| 6 | |
| 7 | use Drupal\Component\Datetime\TimeInterface; |
| 8 | use Drupal\Core\Form\FormAjaxException; |
| 9 | use Drupal\Core\Form\FormState; |
| 10 | use Drupal\Core\Form\FormStateInterface; |
| 11 | use Drupal\Core\Render\RendererInterface; |
| 12 | use Drupal\Core\StringTranslation\TranslatableMarkup; |
| 13 | use Drupal\Core\TempStore\SharedTempStoreFactory; |
| 14 | use Drupal\display_builder\Event\DisplayBuilderEvents; |
| 15 | use Drupal\display_builder\Exception\FormValidationException; |
| 16 | use Drupal\display_builder\InstanceInterface; |
| 17 | use Drupal\display_builder\Island\IslandPluginManagerInterface; |
| 18 | use Drupal\display_builder\Plugin\display_builder\Island\ContextualFormPanel; |
| 19 | use Drupal\display_builder\RenderableBuilderTrait; |
| 20 | use Drupal\display_builder\SourceTree; |
| 21 | use Symfony\Component\DependencyInjection\Attribute\Autowire; |
| 22 | use Symfony\Component\HttpFoundation\Request; |
| 23 | use Symfony\Component\HttpFoundation\RequestStack; |
| 24 | use Symfony\Component\HttpFoundation\Session\SessionInterface; |
| 25 | use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; |
| 26 | |
| 27 | /** |
| 28 | * Returns responses for Display builder routes. |
| 29 | */ |
| 30 | class ApiController extends ApiControllerBase implements ApiControllerInterface { |
| 31 | |
| 32 | use RenderableBuilderTrait; |
| 33 | |
| 34 | public function __construct( |
| 35 | protected EventDispatcherInterface $eventDispatcher, |
| 36 | protected RendererInterface $renderer, |
| 37 | protected TimeInterface $time, |
| 38 | #[Autowire(service: 'tempstore.shared')] |
| 39 | protected SharedTempStoreFactory $sharedTempStoreFactory, |
| 40 | protected SessionInterface $session, |
| 41 | protected RequestStack $requestStack, |
| 42 | private IslandPluginManagerInterface $islandPluginManager, |
| 43 | ) { |
| 44 | parent::__construct($eventDispatcher, $renderer, $time, $sharedTempStoreFactory, $session, $requestStack); |
| 45 | } |
| 46 | |
| 47 | /** |
| 48 | * {@inheritdoc} |
| 49 | */ |
| 50 | public function reloadIsland(Request $request, InstanceInterface $display_builder_instance, string $island_id): array { |
| 51 | $profile = $display_builder_instance->getProfile(); |
| 52 | |
| 53 | if (!isset($profile->getEnabledIslands()[$island_id])) { |
| 54 | $message = $this->t('[reloadIsland] Island @island is not enabled on this profile', ['@island' => $island_id]); |
| 55 | $debug = [ |
| 56 | 'island_id' => $island_id, |
| 57 | 'instance' => $display_builder_instance, |
| 58 | ]; |
| 59 | |
| 60 | return $this->responseMessageError((string) $display_builder_instance->id(), $message, $debug); |
| 61 | } |
| 62 | |
| 63 | $contexts = $display_builder_instance->getAvailableContexts(); |
| 64 | $definitions = \array_intersect_key( |
| 65 | $this->islandPluginManager->getDefinitions(), |
| 66 | [$island_id => TRUE], |
| 67 | ); |
| 68 | $islands = $this->islandPluginManager->createInstances($definitions, $contexts, $profile->getIslandConfigurations()); |
| 69 | $island = $islands[$island_id] ?? NULL; |
| 70 | |
| 71 | if ($island === NULL) { |
| 72 | $message = $this->t('[reloadIsland] Island @island is not available in this context', ['@island' => $island_id]); |
| 73 | $debug = [ |
| 74 | 'island_id' => $island_id, |
| 75 | 'instance' => $display_builder_instance, |
| 76 | ]; |
| 77 | |
| 78 | return $this->responseMessageError((string) $display_builder_instance->id(), $message, $debug); |
| 79 | } |
| 80 | |
| 81 | return [$island->reload($display_builder_instance)]; |
| 82 | } |
| 83 | |
| 84 | /** |
| 85 | * {@inheritdoc} |
| 86 | */ |
| 87 | public function attachToRoot(Request $request, InstanceInterface $display_builder_instance): array { |
| 88 | $position = (int) $request->request->get('position', 0); |
| 89 | |
| 90 | if ($request->request->has('preset_id')) { |
| 91 | $preset_id = (string) $request->request->get('preset_id'); |
| 92 | |
| 93 | return $this->attachPresetToRoot($display_builder_instance, $preset_id, $position); |
| 94 | } |
| 95 | |
| 96 | $is_move = FALSE; |
| 97 | |
| 98 | if ($request->request->has('node_id')) { |
| 99 | $node_id = (string) $request->request->get('node_id'); |
| 100 | |
| 101 | if (!$display_builder_instance->moveToRoot($node_id, $position)) { |
| 102 | $message = $this->t('[attachToRoot] moveToRoot failed with invalid data'); |
| 103 | $debug = [ |
| 104 | 'request' => $request->request->all(), |
| 105 | 'instance' => $display_builder_instance, |
| 106 | ]; |
| 107 | |
| 108 | return $this->responseMessageError((string) $display_builder_instance->id(), $message, $debug); |
| 109 | } |
| 110 | |
| 111 | $is_move = TRUE; |
| 112 | } |
| 113 | elseif ($request->request->has('source_id')) { |
| 114 | $source_id = (string) $request->request->get('source_id'); |
| 115 | $data = $request->request->has('source') ? \json_decode((string) $request->request->get('source'), TRUE) : []; |
| 116 | $node_id = $display_builder_instance->attachToRoot($position, $source_id, $data); |
| 117 | } |
| 118 | else { |
| 119 | $message = '[attachToRoot] Missing content (source_id, node_id or preset_id)'; |
| 120 | $debug = [ |
| 121 | 'request' => $request->request->all(), |
| 122 | 'instance' => $display_builder_instance, |
| 123 | ]; |
| 124 | |
| 125 | return $this->responseMessageError((string) $display_builder_instance->id(), $message, $debug); |
| 126 | } |
| 127 | $display_builder_instance->save(); |
| 128 | |
| 129 | $this->builder = $display_builder_instance; |
| 130 | // Let's refresh when we add new source to get the placeholder replacement. |
| 131 | $this->islandId = $this->resolveSkipIslandId($request, $is_move); |
| 132 | |
| 133 | return $this->dispatchDisplayBuilderEvent( |
| 134 | $is_move ? DisplayBuilderEvents::ON_MOVE : DisplayBuilderEvents::ON_ATTACH_TO_ROOT, |
| 135 | NULL, |
| 136 | $node_id, |
| 137 | ); |
| 138 | } |
| 139 | |
| 140 | /** |
| 141 | * {@inheritdoc} |
| 142 | */ |
| 143 | public function attachToSlot(Request $request, InstanceInterface $display_builder_instance, string $node_id, string $slot): array { |
| 144 | $parent_id = $node_id; |
| 145 | $position = (int) $request->request->get('position', 0); |
| 146 | |
| 147 | if ($request->request->has('preset_id')) { |
| 148 | $preset_id = (string) $request->request->get('preset_id'); |
| 149 | |
| 150 | return $this->attachPresetToSlot($display_builder_instance, $preset_id, $parent_id, $slot, $position); |
| 151 | } |
| 152 | |
| 153 | $is_move = FALSE; |
| 154 | |
| 155 | // First, we update the data state. |
| 156 | if ($request->request->has('node_id')) { |
| 157 | $node_id = (string) $request->request->get('node_id'); |
| 158 | |
| 159 | if (!$display_builder_instance->moveToSlot($node_id, $parent_id, $slot, $position)) { |
| 160 | $message = $this->t('[attachToSlot] moveToSlot failed with invalid data'); |
| 161 | $debug = [ |
| 162 | 'node_id' => $node_id, |
| 163 | 'slot' => $slot, |
| 164 | 'request' => $request->request->all(), |
| 165 | 'instance' => $display_builder_instance, |
| 166 | ]; |
| 167 | |
| 168 | return $this->responseMessageError((string) $display_builder_instance->id(), $message, $debug); |
| 169 | } |
| 170 | |
| 171 | $is_move = TRUE; |
| 172 | } |
| 173 | elseif ($request->request->has('source_id')) { |
| 174 | $source_id = (string) $request->request->get('source_id'); |
| 175 | $data = $request->request->has('source') ? \json_decode((string) $request->request->get('source'), TRUE) : []; |
| 176 | $node_id = $display_builder_instance->attachToSlot($parent_id, $slot, $position, $source_id, $data); |
| 177 | } |
| 178 | else { |
| 179 | $message = $this->t('[attachToSlot] Missing content (component_id, block_id or node_id)'); |
| 180 | $debug = [ |
| 181 | 'node_id' => $node_id, |
| 182 | 'slot' => $slot, |
| 183 | 'request' => $request->request->all(), |
| 184 | 'instance' => $display_builder_instance, |
| 185 | ]; |
| 186 | |
| 187 | return $this->responseMessageError((string) $display_builder_instance->id(), $message, $debug); |
| 188 | } |
| 189 | $display_builder_instance->save(); |
| 190 | |
| 191 | $this->builder = $display_builder_instance; |
| 192 | // Let's refresh when we add new source to get the placeholder replacement. |
| 193 | $this->islandId = $this->resolveSkipIslandId($request, $is_move); |
| 194 | |
| 195 | return $this->dispatchDisplayBuilderEvent( |
| 196 | $is_move ? DisplayBuilderEvents::ON_MOVE : DisplayBuilderEvents::ON_ATTACH_TO_SLOT, |
| 197 | NULL, |
| 198 | $node_id, |
| 199 | $parent_id, |
| 200 | ); |
| 201 | } |
| 202 | |
| 203 | /** |
| 204 | * {@inheritdoc} |
| 205 | */ |
| 206 | public function get(Request $request, InstanceInterface $display_builder_instance, string $node_id): array { |
| 207 | $this->builder = $display_builder_instance; |
| 208 | |
| 209 | return $this->dispatchDisplayBuilderEvent( |
| 210 | DisplayBuilderEvents::ON_ACTIVE, |
| 211 | $display_builder_instance->getNode($node_id), |
| 212 | ); |
| 213 | } |
| 214 | |
| 215 | /** |
| 216 | * {@inheritdoc} |
| 217 | */ |
| 218 | public function update(Request $request, InstanceInterface $display_builder_instance, string $node_id): array { |
| 219 | $this->builder = $display_builder_instance; |
| 220 | $body = $request->getPayload()->all(); |
| 221 | |
| 222 | if (!isset($body['form_id'])) { |
| 223 | $message = $this->t('[update] Missing payload!'); |
| 224 | $debug = [ |
| 225 | 'node_id' => $node_id, |
| 226 | 'request' => $request->request->all(), |
| 227 | 'body' => $body, |
| 228 | 'instance' => $display_builder_instance, |
| 229 | ]; |
| 230 | |
| 231 | return $this->responseMessageError((string) $display_builder_instance->id(), $message, $debug); |
| 232 | } |
| 233 | |
| 234 | // Load the node to properly alter the form data into config data. |
| 235 | $node = $display_builder_instance->getNode($node_id); |
| 236 | |
| 237 | if (isset($body['source']['form_build_id'])) { |
| 238 | unset($body['source']['form_build_id'], $body['source']['form_token'], $body['source']['form_id']); |
| 239 | } |
| 240 | |
| 241 | if (isset($body['form_build_id'])) { |
| 242 | unset($body['form_build_id'], $body['form_token'], $body['form_id']); |
| 243 | } |
| 244 | $form_state = new FormState(); |
| 245 | // Default values are the existing values from the state. |
| 246 | $form_state->addBuildInfo('args', [ |
| 247 | [ |
| 248 | 'island_id' => 'contextual_form', |
| 249 | 'builder_id' => (string) $display_builder_instance->id(), |
| 250 | 'instance' => $node, |
| 251 | ], |
| 252 | $display_builder_instance->getAvailableContexts(), |
| 253 | ]); |
| 254 | $form_state->setTemporaryValue('gathered_contexts', $display_builder_instance->getAvailableContexts()); |
| 255 | // The body received corresponds to raw form values. |
| 256 | // We need to set them in the form state to properly |
| 257 | // take them into account. |
| 258 | $form_state->setValues($body); |
| 259 | |
| 260 | $formClass = ContextualFormPanel::getFormClass(); |
| 261 | $data = []; |
| 262 | |
| 263 | try { |
| 264 | $values = $this->validateIslandForm($formClass, $form_state); |
| 265 | $data['source'] = $values; |
| 266 | } |
| 267 | catch (FormAjaxException $e) { |
| 268 | throw $e; |
| 269 | } |
| 270 | catch (\Exception $e) { |
| 271 | $debug = [ |
| 272 | 'node_id' => $node_id, |
| 273 | 'request' => $request->request->all(), |
| 274 | 'form' => $form_state->getValues(), |
| 275 | 'body' => $body, |
| 276 | 'instance' => $display_builder_instance, |
| 277 | ]; |
| 278 | |
| 279 | return $this->responseMessageError((string) $display_builder_instance->id(), $e->getMessage(), $debug); |
| 280 | } |
| 281 | |
| 282 | $display_builder_instance->setSource($node_id, $node['source_id'], $data['source']); |
| 283 | $display_builder_instance->save(); |
| 284 | |
| 285 | $this->builder = $display_builder_instance; |
| 286 | $this->islandId = (string) $request->query->get('from', NULL); |
| 287 | |
| 288 | return $this->dispatchDisplayBuilderEvent( |
| 289 | DisplayBuilderEvents::ON_UPDATE, |
| 290 | NULL, |
| 291 | $node_id, |
| 292 | ); |
| 293 | } |
| 294 | |
| 295 | /** |
| 296 | * {@inheritdoc} |
| 297 | */ |
| 298 | public function thirdPartySettingsUpdate(Request $request, InstanceInterface $display_builder_instance, string $node_id, string $island_id): array { |
| 299 | $body = $request->getPayload()->all(); |
| 300 | |
| 301 | if (!isset($body['form_id'])) { |
| 302 | $message = $this->t('[thirdPartySettingsUpdate] Missing payload!'); |
| 303 | $debug = [ |
| 304 | 'node_id' => $node_id, |
| 305 | 'island_id' => $island_id, |
| 306 | 'request' => $request->request->all(), |
| 307 | 'body' => $body, |
| 308 | 'instance' => $display_builder_instance, |
| 309 | ]; |
| 310 | |
| 311 | return $this->responseMessageError((string) $display_builder_instance->id(), $message, $debug); |
| 312 | } |
| 313 | |
| 314 | $islandDefinition = $this->islandPluginManager->getDefinition($island_id); |
| 315 | // Load the instance to properly alter the form data into config data. |
| 316 | $node = $display_builder_instance->getNode($node_id); |
| 317 | unset($body['form_build_id'], $body['form_token'], $body['form_id']); |
| 318 | |
| 319 | $form_state = new FormState(); |
| 320 | // Default values are the existing values from the state. |
| 321 | $form_state->addBuildInfo('args', [ |
| 322 | [ |
| 323 | 'island_id' => $island_id, |
| 324 | 'builder_id' => (string) $display_builder_instance->id(), |
| 325 | 'instance' => $node, |
| 326 | ], |
| 327 | [], |
| 328 | ]); |
| 329 | |
| 330 | // phpcs:disable Drupal.Files.LineLength.TooLong |
| 331 | // @todo should context be injected for third party settings? |
| 332 | // $form_state->setTemporaryValue('gathered_contexts', $display_builder_instance->getAvailableContexts()); |
| 333 | // phpcs:enable Drupal.Files.LineLength.TooLong |
| 334 | // The body received corresponds to raw form values. |
| 335 | // We need to set them in the form state to properly |
| 336 | // take them into account. |
| 337 | $form_state->setValues($body); |
| 338 | |
| 339 | $formClass = ($islandDefinition['class'])::getFormClass(); |
| 340 | $values = $this->validateIslandForm($formClass, $form_state); |
| 341 | // We update the state with the new data. |
| 342 | $display_builder_instance->setThirdPartySettings($node_id, $island_id, $values); |
| 343 | $display_builder_instance->save(); |
| 344 | |
| 345 | $this->builder = $display_builder_instance; |
| 346 | $this->islandId = $island_id; |
| 347 | |
| 348 | return $this->dispatchDisplayBuilderEvent( |
| 349 | DisplayBuilderEvents::ON_UPDATE, |
| 350 | NULL, |
| 351 | $node_id, |
| 352 | NULL, |
| 353 | ); |
| 354 | } |
| 355 | |
| 356 | /** |
| 357 | * {@inheritdoc} |
| 358 | */ |
| 359 | public function undo(Request $request, InstanceInterface $display_builder_instance): array { |
| 360 | /** @var \Drupal\display_builder\Entity\InstanceStorage $storage */ |
| 361 | $storage = $this->entityTypeManager()->getStorage('display_builder_instance'); |
| 362 | /** @var \Drupal\display_builder\InstanceInterface $display_builder_instance */ |
| 363 | $display_builder_instance = $storage->undo($display_builder_instance); |
| 364 | |
| 365 | $this->builder = $display_builder_instance; |
| 366 | |
| 367 | return $this->dispatchDisplayBuilderEvent(DisplayBuilderEvents::ON_HISTORY_CHANGE); |
| 368 | } |
| 369 | |
| 370 | /** |
| 371 | * {@inheritdoc} |
| 372 | */ |
| 373 | public function redo(Request $request, InstanceInterface $display_builder_instance): array { |
| 374 | /** @var \Drupal\display_builder\Entity\InstanceStorage $storage */ |
| 375 | $storage = $this->entityTypeManager()->getStorage('display_builder_instance'); |
| 376 | /** @var \Drupal\display_builder\InstanceInterface $display_builder_instance */ |
| 377 | $display_builder_instance = $storage->redo($display_builder_instance); |
| 378 | |
| 379 | $this->builder = $display_builder_instance; |
| 380 | |
| 381 | return $this->dispatchDisplayBuilderEvent(DisplayBuilderEvents::ON_HISTORY_CHANGE); |
| 382 | } |
| 383 | |
| 384 | /** |
| 385 | * {@inheritdoc} |
| 386 | */ |
| 387 | public function clear(Request $request, InstanceInterface $display_builder_instance): array { |
| 388 | $display_builder_instance->clear(); |
| 389 | $display_builder_instance->save(); |
| 390 | |
| 391 | $this->builder = $display_builder_instance; |
| 392 | |
| 393 | return $this->dispatchDisplayBuilderEvent(DisplayBuilderEvents::ON_HISTORY_CHANGE); |
| 394 | } |
| 395 | |
| 396 | /** |
| 397 | * Attach a pattern preset to root. |
| 398 | * |
| 399 | * Presets are "resolved" after attachment, so they are never moved around. |
| 400 | * |
| 401 | * @param \Drupal\display_builder\InstanceInterface $display_builder_instance |
| 402 | * Display builder instance. |
| 403 | * @param string $preset_id |
| 404 | * Pattern preset ID. |
| 405 | * @param int $position |
| 406 | * Position. |
| 407 | * |
| 408 | * @return array |
| 409 | * A renderable array. |
| 410 | */ |
| 411 | protected function attachPresetToRoot(InstanceInterface $display_builder_instance, string $preset_id, int $position): array { |
| 412 | $presetStorage = $this->entityTypeManager()->getStorage('pattern_preset'); |
| 413 | |
| 414 | /** @var \Drupal\display_builder\Entity\PatternPresetInterface $preset */ |
| 415 | $preset = $presetStorage->load($preset_id); |
| 416 | $data = $preset->getSources(); |
| 417 | |
| 418 | if (!isset($data['source_id']) || !isset($data['source'])) { |
| 419 | $message = $this->t('[attachToRoot] Missing preset source_id data'); |
| 420 | $debug = [ |
| 421 | 'preset_id' => $preset_id, |
| 422 | 'position' => $position, |
| 423 | 'data' => $data, |
| 424 | 'instance' => $display_builder_instance, |
| 425 | ]; |
| 426 | |
| 427 | return $this->responseMessageError((string) $display_builder_instance->id(), $message, $debug); |
| 428 | } |
| 429 | $node_id = $display_builder_instance->attachToRoot($position, $data['source_id'], $data['source']); |
| 430 | |
| 431 | foreach ($data['third_party_settings'] ?? [] as $provider => $settings) { |
| 432 | $display_builder_instance->setThirdPartySettings($node_id, $provider, $settings ?? []); |
| 433 | } |
| 434 | $display_builder_instance->save(); |
| 435 | $this->builder = $display_builder_instance; |
| 436 | |
| 437 | return $this->dispatchDisplayBuilderEvent( |
| 438 | DisplayBuilderEvents::ON_ATTACH_TO_ROOT, |
| 439 | NULL, |
| 440 | $node_id, |
| 441 | ); |
| 442 | } |
| 443 | |
| 444 | /** |
| 445 | * Attach a pattern preset to a slot . |
| 446 | * |
| 447 | * Presets are "resolved" after attachment, so they are never moved around. |
| 448 | * |
| 449 | * @param \Drupal\display_builder\InstanceInterface $display_builder_instance |
| 450 | * Display builder instance. |
| 451 | * @param string $preset_id |
| 452 | * Pattern preset ID. |
| 453 | * @param string $parent_id |
| 454 | * Parent instance ID. |
| 455 | * @param string $slot |
| 456 | * Slot. |
| 457 | * @param int $position |
| 458 | * Position. |
| 459 | * |
| 460 | * @return array |
| 461 | * A renderable array. |
| 462 | */ |
| 463 | protected function attachPresetToSlot(InstanceInterface $display_builder_instance, string $preset_id, string $parent_id, string $slot, int $position): array { |
| 464 | $presetStorage = $this->entityTypeManager()->getStorage('pattern_preset'); |
| 465 | |
| 466 | /** @var \Drupal\display_builder\Entity\PatternPresetInterface $preset */ |
| 467 | $preset = $presetStorage->load($preset_id); |
| 468 | $data = $preset->getSources(); |
| 469 | |
| 470 | if (!isset($data['source_id']) || !isset($data['source'])) { |
| 471 | $message = $this->t('[attachToSlot] Missing preset source_id data'); |
| 472 | $debug = [ |
| 473 | 'preset_id' => $preset_id, |
| 474 | 'parent_id' => $parent_id, |
| 475 | 'slot' => $slot, |
| 476 | 'position' => $position, |
| 477 | 'data' => $data, |
| 478 | 'instance' => $display_builder_instance, |
| 479 | ]; |
| 480 | |
| 481 | return $this->responseMessageError((string) $display_builder_instance->id(), $message, $debug); |
| 482 | } |
| 483 | $node_id = $display_builder_instance->attachToSlot($parent_id, $slot, $position, $data['source_id'], $data['source']); |
| 484 | |
| 485 | foreach ($data['third_party_settings'] ?? [] as $provider => $settings) { |
| 486 | $display_builder_instance->setThirdPartySettings($node_id, $provider, $settings ?? []); |
| 487 | } |
| 488 | |
| 489 | $display_builder_instance->save(); |
| 490 | $this->builder = $display_builder_instance; |
| 491 | |
| 492 | return $this->dispatchDisplayBuilderEvent( |
| 493 | DisplayBuilderEvents::ON_ATTACH_TO_ROOT, |
| 494 | NULL, |
| 495 | $node_id, |
| 496 | ); |
| 497 | } |
| 498 | |
| 499 | /** |
| 500 | * Determines which island (if any) to skip from the event dispatch fan-out. |
| 501 | * |
| 502 | * Builder, Wireframe, and Tree share the same Sortable group (@see |
| 503 | * components/dropzone/dropzone.js), so an existing node can be dragged |
| 504 | * from one panel's dropzone straight into another's. Sortable only |
| 505 | * relocates the DOM node - it never re-renders it - so after a |
| 506 | * cross-panel move the dropped element is still wearing whichever |
| 507 | * panel originally rendered it (@see |
| 508 | * BuilderPanel::buildNodeAttributes(), display_builder.js's addVals()). |
| 509 | * Skipping the destination island's own re-render is only safe when the |
| 510 | * move stayed within that same island; otherwise its dropzone would be |
| 511 | * left showing another island's markup until the next full reload. |
| 512 | * |
| 513 | * @param \Symfony\Component\HttpFoundation\Request $request |
| 514 | * The request. |
| 515 | * @param bool $is_move |
| 516 | * Whether this attach is actually moving an existing node. |
| 517 | * |
| 518 | * @return string|null |
| 519 | * The island ID to skip, or NULL to skip none. |
| 520 | */ |
| 521 | private function resolveSkipIslandId(Request $request, bool $is_move): ?string { |
| 522 | if (!$is_move) { |
| 523 | return NULL; |
| 524 | } |
| 525 | |
| 526 | $destination_island = (string) $request->query->get('from', ''); |
| 527 | $source_island = (string) $request->request->get('source_island', ''); |
| 528 | |
| 529 | return ($source_island !== '' && $source_island === $destination_island) ? $destination_island : NULL; |
| 530 | } |
| 531 | |
| 532 | /** |
| 533 | * Validates an island form. |
| 534 | * |
| 535 | * @param string $formClass |
| 536 | * The form class. |
| 537 | * @param \Drupal\Core\Form\FormStateInterface $form_state |
| 538 | * The form state. |
| 539 | * |
| 540 | * @return array |
| 541 | * The validated values. |
| 542 | */ |
| 543 | private function validateIslandForm(string $formClass, FormStateInterface $form_state): array { |
| 544 | /** @var \Drupal\Core\Form\FormBuilder $formBuilder */ |
| 545 | $formBuilder = $this->formBuilder(); |
| 546 | |
| 547 | try { |
| 548 | $triggering_element = $form_state->getTriggeringElement(); |
| 549 | |
| 550 | if (!$triggering_element && !isset($form_state->getValues()['_triggering_element_name'])) { |
| 551 | // We set a fake triggering element to avoid form API error. |
| 552 | $form_state->setTriggeringElement([ |
| 553 | '#type' => 'submit', |
| 554 | '#limit_validation_errors' => FALSE, |
| 555 | '#value' => (string) $this->t('Submit'), |
| 556 | ]); |
| 557 | } |
| 558 | $form = $formBuilder->buildForm($formClass, $form_state); |
| 559 | $formErrors = $form_state->getErrors(); |
| 560 | |
| 561 | if (!empty($formErrors)) { |
| 562 | $first_error = \reset($formErrors); |
| 563 | |
| 564 | throw new FormValidationException((string) $first_error); |
| 565 | } |
| 566 | $formBuilder->validateForm($formClass, $form, $form_state); |
| 567 | $formErrors = $form_state->getErrors(); |
| 568 | |
| 569 | if (!empty($formErrors)) { |
| 570 | $first_error = \reset($formErrors); |
| 571 | |
| 572 | throw new FormValidationException((string) $first_error); |
| 573 | } |
| 574 | } |
| 575 | catch (FormAjaxException $e) { |
| 576 | throw $e; |
| 577 | } |
| 578 | // Those values are the validated values, produced by the form. |
| 579 | // with all Form API processing. |
| 580 | $values = $form_state->getValues(); |
| 581 | |
| 582 | // We clean the values from form API keys. |
| 583 | if (isset($values['form_build_id'])) { |
| 584 | unset($values['form_build_id'], $values['form_token'], $values['form_id']); |
| 585 | } |
| 586 | |
| 587 | return $values; |
| 588 | } |
| 589 | |
| 590 | /** |
| 591 | * Render an error message in the display builder. |
| 592 | * |
| 593 | * @param string $display_builder_instance_id |
| 594 | * The builder ID. |
| 595 | * @param string|\Drupal\Core\StringTranslation\TranslatableMarkup $message |
| 596 | * The error message. |
| 597 | * @param array $debug |
| 598 | * The debug code related to the error. |
| 599 | * |
| 600 | * @return array |
| 601 | * A renderable array. |
| 602 | */ |
| 603 | private function responseMessageError( |
| 604 | string $display_builder_instance_id, |
| 605 | string|TranslatableMarkup $message, |
| 606 | array $debug, |
| 607 | ): array { |
| 608 | // Reduce verbosity. |
| 609 | unset($debug['request']['ajax_page_state']['libraries']); |
| 610 | |
| 611 | $instance = $debug['instance'] ?? NULL; |
| 612 | |
| 613 | if ($instance) { |
| 614 | $tree = new SourceTree($debug['instance']->getCurrentState()); |
| 615 | unset($debug['instance']); |
| 616 | $debug['tree'] = $tree->getNormalizedStructure()['structure']; |
| 617 | } |
| 618 | |
| 619 | $this->getLogger('display_builder')->error('@message <pre>@debug</pre>', [ |
| 620 | '@message' => $message, |
| 621 | '@debug' => \print_r($debug, TRUE), |
| 622 | ]); |
| 623 | |
| 624 | $message = new TranslatableMarkup('An error occurred, refresh the current page to continue from a valid state.<br>Check logs for more details.'); |
| 625 | |
| 626 | return $this->buildError($display_builder_instance_id, $message, TRUE); |
| 627 | } |
| 628 | |
| 629 | } |
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.
| 411 | protected function attachPresetToRoot(InstanceInterface $display_builder_instance, string $preset_id, int $position): array { |
| 412 | $presetStorage = $this->entityTypeManager()->getStorage('pattern_preset'); |
| 413 | |
| 414 | /** @var \Drupal\display_builder\Entity\PatternPresetInterface $preset */ |
| 415 | $preset = $presetStorage->load($preset_id); |
| 416 | $data = $preset->getSources(); |
| 417 | |
| 418 | if (!isset($data['source_id']) || !isset($data['source'])) { |
| 418 | if (!isset($data['source_id']) || !isset($data['source'])) { |
| 418 | if (!isset($data['source_id']) || !isset($data['source'])) { |
| 419 | $message = $this->t('[attachToRoot] Missing preset source_id data'); |
| 420 | $debug = [ |
| 421 | 'preset_id' => $preset_id, |
| 422 | 'position' => $position, |
| 423 | 'data' => $data, |
| 424 | 'instance' => $display_builder_instance, |
| 425 | ]; |
| 426 | |
| 427 | return $this->responseMessageError((string) $display_builder_instance->id(), $message, $debug); |
| 429 | $node_id = $display_builder_instance->attachToRoot($position, $data['source_id'], $data['source']); |
| 430 | |
| 431 | foreach ($data['third_party_settings'] ?? [] as $provider => $settings) { |
| 431 | foreach ($data['third_party_settings'] ?? [] as $provider => $settings) { |
| 431 | foreach ($data['third_party_settings'] ?? [] as $provider => $settings) { |
| 431 | foreach ($data['third_party_settings'] ?? [] as $provider => $settings) { |
| 432 | $display_builder_instance->setThirdPartySettings($node_id, $provider, $settings ?? []); |
| 433 | } |
| 434 | $display_builder_instance->save(); |
| 435 | $this->builder = $display_builder_instance; |
| 436 | |
| 437 | return $this->dispatchDisplayBuilderEvent( |
| 438 | DisplayBuilderEvents::ON_ATTACH_TO_ROOT, |
| 439 | NULL, |
| 440 | $node_id, |
| 441 | ); |
| 442 | } |
| 463 | protected function attachPresetToSlot(InstanceInterface $display_builder_instance, string $preset_id, string $parent_id, string $slot, int $position): array { |
| 464 | $presetStorage = $this->entityTypeManager()->getStorage('pattern_preset'); |
| 465 | |
| 466 | /** @var \Drupal\display_builder\Entity\PatternPresetInterface $preset */ |
| 467 | $preset = $presetStorage->load($preset_id); |
| 468 | $data = $preset->getSources(); |
| 469 | |
| 470 | if (!isset($data['source_id']) || !isset($data['source'])) { |
| 470 | if (!isset($data['source_id']) || !isset($data['source'])) { |
| 470 | if (!isset($data['source_id']) || !isset($data['source'])) { |
| 471 | $message = $this->t('[attachToSlot] Missing preset source_id data'); |
| 472 | $debug = [ |
| 473 | 'preset_id' => $preset_id, |
| 474 | 'parent_id' => $parent_id, |
| 475 | 'slot' => $slot, |
| 476 | 'position' => $position, |
| 477 | 'data' => $data, |
| 478 | 'instance' => $display_builder_instance, |
| 479 | ]; |
| 480 | |
| 481 | return $this->responseMessageError((string) $display_builder_instance->id(), $message, $debug); |
| 483 | $node_id = $display_builder_instance->attachToSlot($parent_id, $slot, $position, $data['source_id'], $data['source']); |
| 484 | |
| 485 | foreach ($data['third_party_settings'] ?? [] as $provider => $settings) { |
| 485 | foreach ($data['third_party_settings'] ?? [] as $provider => $settings) { |
| 485 | foreach ($data['third_party_settings'] ?? [] as $provider => $settings) { |
| 485 | foreach ($data['third_party_settings'] ?? [] as $provider => $settings) { |
| 486 | $display_builder_instance->setThirdPartySettings($node_id, $provider, $settings ?? []); |
| 487 | } |
| 488 | |
| 489 | $display_builder_instance->save(); |
| 490 | $this->builder = $display_builder_instance; |
| 491 | |
| 492 | return $this->dispatchDisplayBuilderEvent( |
| 493 | DisplayBuilderEvents::ON_ATTACH_TO_ROOT, |
| 494 | NULL, |
| 495 | $node_id, |
| 496 | ); |
| 497 | } |
| 87 | public function attachToRoot(Request $request, InstanceInterface $display_builder_instance): array { |
| 88 | $position = (int) $request->request->get('position', 0); |
| 89 | |
| 90 | if ($request->request->has('preset_id')) { |
| 91 | $preset_id = (string) $request->request->get('preset_id'); |
| 92 | |
| 93 | return $this->attachPresetToRoot($display_builder_instance, $preset_id, $position); |
| 96 | $is_move = FALSE; |
| 97 | |
| 98 | if ($request->request->has('node_id')) { |
| 99 | $node_id = (string) $request->request->get('node_id'); |
| 100 | |
| 101 | if (!$display_builder_instance->moveToRoot($node_id, $position)) { |
| 102 | $message = $this->t('[attachToRoot] moveToRoot failed with invalid data'); |
| 103 | $debug = [ |
| 104 | 'request' => $request->request->all(), |
| 105 | 'instance' => $display_builder_instance, |
| 106 | ]; |
| 107 | |
| 108 | return $this->responseMessageError((string) $display_builder_instance->id(), $message, $debug); |
| 98 | if ($request->request->has('node_id')) { |
| 99 | $node_id = (string) $request->request->get('node_id'); |
| 100 | |
| 101 | if (!$display_builder_instance->moveToRoot($node_id, $position)) { |
| 102 | $message = $this->t('[attachToRoot] moveToRoot failed with invalid data'); |
| 103 | $debug = [ |
| 104 | 'request' => $request->request->all(), |
| 105 | 'instance' => $display_builder_instance, |
| 106 | ]; |
| 107 | |
| 108 | return $this->responseMessageError((string) $display_builder_instance->id(), $message, $debug); |
| 109 | } |
| 110 | |
| 111 | $is_move = TRUE; |
| 113 | elseif ($request->request->has('source_id')) { |
| 114 | $source_id = (string) $request->request->get('source_id'); |
| 115 | $data = $request->request->has('source') ? \json_decode((string) $request->request->get('source'), TRUE) : []; |
| 115 | $data = $request->request->has('source') ? \json_decode((string) $request->request->get('source'), TRUE) : []; |
| 115 | $data = $request->request->has('source') ? \json_decode((string) $request->request->get('source'), TRUE) : []; |
| 113 | elseif ($request->request->has('source_id')) { |
| 114 | $source_id = (string) $request->request->get('source_id'); |
| 115 | $data = $request->request->has('source') ? \json_decode((string) $request->request->get('source'), TRUE) : []; |
| 119 | $message = '[attachToRoot] Missing content (source_id, node_id or preset_id)'; |
| 120 | $debug = [ |
| 121 | 'request' => $request->request->all(), |
| 122 | 'instance' => $display_builder_instance, |
| 123 | ]; |
| 124 | |
| 125 | return $this->responseMessageError((string) $display_builder_instance->id(), $message, $debug); |
| 127 | $display_builder_instance->save(); |
| 128 | |
| 129 | $this->builder = $display_builder_instance; |
| 130 | // Let's refresh when we add new source to get the placeholder replacement. |
| 131 | $this->islandId = $this->resolveSkipIslandId($request, $is_move); |
| 132 | |
| 133 | return $this->dispatchDisplayBuilderEvent( |
| 134 | $is_move ? DisplayBuilderEvents::ON_MOVE : DisplayBuilderEvents::ON_ATTACH_TO_ROOT, |
| 134 | $is_move ? DisplayBuilderEvents::ON_MOVE : DisplayBuilderEvents::ON_ATTACH_TO_ROOT, |
| 134 | $is_move ? DisplayBuilderEvents::ON_MOVE : DisplayBuilderEvents::ON_ATTACH_TO_ROOT, |
| 134 | $is_move ? DisplayBuilderEvents::ON_MOVE : DisplayBuilderEvents::ON_ATTACH_TO_ROOT, |
| 135 | NULL, |
| 136 | $node_id, |
| 137 | ); |
| 138 | } |
| 143 | public function attachToSlot(Request $request, InstanceInterface $display_builder_instance, string $node_id, string $slot): array { |
| 144 | $parent_id = $node_id; |
| 145 | $position = (int) $request->request->get('position', 0); |
| 146 | |
| 147 | if ($request->request->has('preset_id')) { |
| 148 | $preset_id = (string) $request->request->get('preset_id'); |
| 149 | |
| 150 | return $this->attachPresetToSlot($display_builder_instance, $preset_id, $parent_id, $slot, $position); |
| 153 | $is_move = FALSE; |
| 154 | |
| 155 | // First, we update the data state. |
| 156 | if ($request->request->has('node_id')) { |
| 157 | $node_id = (string) $request->request->get('node_id'); |
| 158 | |
| 159 | if (!$display_builder_instance->moveToSlot($node_id, $parent_id, $slot, $position)) { |
| 160 | $message = $this->t('[attachToSlot] moveToSlot failed with invalid data'); |
| 161 | $debug = [ |
| 162 | 'node_id' => $node_id, |
| 163 | 'slot' => $slot, |
| 164 | 'request' => $request->request->all(), |
| 165 | 'instance' => $display_builder_instance, |
| 166 | ]; |
| 167 | |
| 168 | return $this->responseMessageError((string) $display_builder_instance->id(), $message, $debug); |
| 156 | if ($request->request->has('node_id')) { |
| 157 | $node_id = (string) $request->request->get('node_id'); |
| 158 | |
| 159 | if (!$display_builder_instance->moveToSlot($node_id, $parent_id, $slot, $position)) { |
| 160 | $message = $this->t('[attachToSlot] moveToSlot failed with invalid data'); |
| 161 | $debug = [ |
| 162 | 'node_id' => $node_id, |
| 163 | 'slot' => $slot, |
| 164 | 'request' => $request->request->all(), |
| 165 | 'instance' => $display_builder_instance, |
| 166 | ]; |
| 167 | |
| 168 | return $this->responseMessageError((string) $display_builder_instance->id(), $message, $debug); |
| 169 | } |
| 170 | |
| 171 | $is_move = TRUE; |
| 173 | elseif ($request->request->has('source_id')) { |
| 174 | $source_id = (string) $request->request->get('source_id'); |
| 175 | $data = $request->request->has('source') ? \json_decode((string) $request->request->get('source'), TRUE) : []; |
| 175 | $data = $request->request->has('source') ? \json_decode((string) $request->request->get('source'), TRUE) : []; |
| 175 | $data = $request->request->has('source') ? \json_decode((string) $request->request->get('source'), TRUE) : []; |
| 173 | elseif ($request->request->has('source_id')) { |
| 174 | $source_id = (string) $request->request->get('source_id'); |
| 175 | $data = $request->request->has('source') ? \json_decode((string) $request->request->get('source'), TRUE) : []; |
| 179 | $message = $this->t('[attachToSlot] Missing content (component_id, block_id or node_id)'); |
| 180 | $debug = [ |
| 181 | 'node_id' => $node_id, |
| 182 | 'slot' => $slot, |
| 183 | 'request' => $request->request->all(), |
| 184 | 'instance' => $display_builder_instance, |
| 185 | ]; |
| 186 | |
| 187 | return $this->responseMessageError((string) $display_builder_instance->id(), $message, $debug); |
| 189 | $display_builder_instance->save(); |
| 190 | |
| 191 | $this->builder = $display_builder_instance; |
| 192 | // Let's refresh when we add new source to get the placeholder replacement. |
| 193 | $this->islandId = $this->resolveSkipIslandId($request, $is_move); |
| 194 | |
| 195 | return $this->dispatchDisplayBuilderEvent( |
| 196 | $is_move ? DisplayBuilderEvents::ON_MOVE : DisplayBuilderEvents::ON_ATTACH_TO_SLOT, |
| 196 | $is_move ? DisplayBuilderEvents::ON_MOVE : DisplayBuilderEvents::ON_ATTACH_TO_SLOT, |
| 196 | $is_move ? DisplayBuilderEvents::ON_MOVE : DisplayBuilderEvents::ON_ATTACH_TO_SLOT, |
| 196 | $is_move ? DisplayBuilderEvents::ON_MOVE : DisplayBuilderEvents::ON_ATTACH_TO_SLOT, |
| 197 | NULL, |
| 198 | $node_id, |
| 199 | $parent_id, |
| 200 | ); |
| 201 | } |
| 387 | public function clear(Request $request, InstanceInterface $display_builder_instance): array { |
| 388 | $display_builder_instance->clear(); |
| 389 | $display_builder_instance->save(); |
| 390 | |
| 391 | $this->builder = $display_builder_instance; |
| 392 | |
| 393 | return $this->dispatchDisplayBuilderEvent(DisplayBuilderEvents::ON_HISTORY_CHANGE); |
| 394 | } |
| 206 | public function get(Request $request, InstanceInterface $display_builder_instance, string $node_id): array { |
| 207 | $this->builder = $display_builder_instance; |
| 208 | |
| 209 | return $this->dispatchDisplayBuilderEvent( |
| 210 | DisplayBuilderEvents::ON_ACTIVE, |
| 211 | $display_builder_instance->getNode($node_id), |
| 212 | ); |
| 213 | } |
| 373 | public function redo(Request $request, InstanceInterface $display_builder_instance): array { |
| 374 | /** @var \Drupal\display_builder\Entity\InstanceStorage $storage */ |
| 375 | $storage = $this->entityTypeManager()->getStorage('display_builder_instance'); |
| 376 | /** @var \Drupal\display_builder\InstanceInterface $display_builder_instance */ |
| 377 | $display_builder_instance = $storage->redo($display_builder_instance); |
| 378 | |
| 379 | $this->builder = $display_builder_instance; |
| 380 | |
| 381 | return $this->dispatchDisplayBuilderEvent(DisplayBuilderEvents::ON_HISTORY_CHANGE); |
| 382 | } |
| 50 | public function reloadIsland(Request $request, InstanceInterface $display_builder_instance, string $island_id): array { |
| 51 | $profile = $display_builder_instance->getProfile(); |
| 52 | |
| 53 | if (!isset($profile->getEnabledIslands()[$island_id])) { |
| 54 | $message = $this->t('[reloadIsland] Island @island is not enabled on this profile', ['@island' => $island_id]); |
| 55 | $debug = [ |
| 56 | 'island_id' => $island_id, |
| 57 | 'instance' => $display_builder_instance, |
| 58 | ]; |
| 59 | |
| 60 | return $this->responseMessageError((string) $display_builder_instance->id(), $message, $debug); |
| 63 | $contexts = $display_builder_instance->getAvailableContexts(); |
| 64 | $definitions = \array_intersect_key( |
| 65 | $this->islandPluginManager->getDefinitions(), |
| 66 | [$island_id => TRUE], |
| 67 | ); |
| 68 | $islands = $this->islandPluginManager->createInstances($definitions, $contexts, $profile->getIslandConfigurations()); |
| 69 | $island = $islands[$island_id] ?? NULL; |
| 70 | |
| 71 | if ($island === NULL) { |
| 72 | $message = $this->t('[reloadIsland] Island @island is not available in this context', ['@island' => $island_id]); |
| 73 | $debug = [ |
| 74 | 'island_id' => $island_id, |
| 75 | 'instance' => $display_builder_instance, |
| 76 | ]; |
| 77 | |
| 78 | return $this->responseMessageError((string) $display_builder_instance->id(), $message, $debug); |
| 81 | return [$island->reload($display_builder_instance)]; |
| 82 | } |
| 521 | private function resolveSkipIslandId(Request $request, bool $is_move): ?string { |
| 522 | if (!$is_move) { |
| 523 | return NULL; |
| 526 | $destination_island = (string) $request->query->get('from', ''); |
| 527 | $source_island = (string) $request->request->get('source_island', ''); |
| 528 | |
| 529 | return ($source_island !== '' && $source_island === $destination_island) ? $destination_island : NULL; |
| 529 | return ($source_island !== '' && $source_island === $destination_island) ? $destination_island : NULL; |
| 529 | return ($source_island !== '' && $source_island === $destination_island) ? $destination_island : NULL; |
| 529 | return ($source_island !== '' && $source_island === $destination_island) ? $destination_island : NULL; |
| 529 | return ($source_island !== '' && $source_island === $destination_island) ? $destination_island : NULL; |
| 529 | return ($source_island !== '' && $source_island === $destination_island) ? $destination_island : NULL; |
| 530 | } |
| 604 | string $display_builder_instance_id, |
| 605 | string|TranslatableMarkup $message, |
| 606 | array $debug, |
| 607 | ): array { |
| 608 | // Reduce verbosity. |
| 609 | unset($debug['request']['ajax_page_state']['libraries']); |
| 610 | |
| 611 | $instance = $debug['instance'] ?? NULL; |
| 612 | |
| 613 | if ($instance) { |
| 614 | $tree = new SourceTree($debug['instance']->getCurrentState()); |
| 615 | unset($debug['instance']); |
| 616 | $debug['tree'] = $tree->getNormalizedStructure()['structure']; |
| 617 | } |
| 618 | |
| 619 | $this->getLogger('display_builder')->error('@message <pre>@debug</pre>', [ |
| 619 | $this->getLogger('display_builder')->error('@message <pre>@debug</pre>', [ |
| 620 | '@message' => $message, |
| 621 | '@debug' => \print_r($debug, TRUE), |
| 622 | ]); |
| 623 | |
| 624 | $message = new TranslatableMarkup('An error occurred, refresh the current page to continue from a valid state.<br>Check logs for more details.'); |
| 625 | |
| 626 | return $this->buildError($display_builder_instance_id, $message, TRUE); |
| 627 | } |
| 298 | public function thirdPartySettingsUpdate(Request $request, InstanceInterface $display_builder_instance, string $node_id, string $island_id): array { |
| 299 | $body = $request->getPayload()->all(); |
| 300 | |
| 301 | if (!isset($body['form_id'])) { |
| 302 | $message = $this->t('[thirdPartySettingsUpdate] Missing payload!'); |
| 303 | $debug = [ |
| 304 | 'node_id' => $node_id, |
| 305 | 'island_id' => $island_id, |
| 306 | 'request' => $request->request->all(), |
| 307 | 'body' => $body, |
| 308 | 'instance' => $display_builder_instance, |
| 309 | ]; |
| 310 | |
| 311 | return $this->responseMessageError((string) $display_builder_instance->id(), $message, $debug); |
| 314 | $islandDefinition = $this->islandPluginManager->getDefinition($island_id); |
| 315 | // Load the instance to properly alter the form data into config data. |
| 316 | $node = $display_builder_instance->getNode($node_id); |
| 317 | unset($body['form_build_id'], $body['form_token'], $body['form_id']); |
| 318 | |
| 319 | $form_state = new FormState(); |
| 320 | // Default values are the existing values from the state. |
| 321 | $form_state->addBuildInfo('args', [ |
| 322 | [ |
| 323 | 'island_id' => $island_id, |
| 324 | 'builder_id' => (string) $display_builder_instance->id(), |
| 325 | 'instance' => $node, |
| 326 | ], |
| 327 | [], |
| 328 | ]); |
| 329 | |
| 330 | // phpcs:disable Drupal.Files.LineLength.TooLong |
| 331 | // @todo should context be injected for third party settings? |
| 332 | // $form_state->setTemporaryValue('gathered_contexts', $display_builder_instance->getAvailableContexts()); |
| 333 | // phpcs:enable Drupal.Files.LineLength.TooLong |
| 334 | // The body received corresponds to raw form values. |
| 335 | // We need to set them in the form state to properly |
| 336 | // take them into account. |
| 337 | $form_state->setValues($body); |
| 338 | |
| 339 | $formClass = ($islandDefinition['class'])::getFormClass(); |
| 340 | $values = $this->validateIslandForm($formClass, $form_state); |
| 341 | // We update the state with the new data. |
| 342 | $display_builder_instance->setThirdPartySettings($node_id, $island_id, $values); |
| 343 | $display_builder_instance->save(); |
| 344 | |
| 345 | $this->builder = $display_builder_instance; |
| 346 | $this->islandId = $island_id; |
| 347 | |
| 348 | return $this->dispatchDisplayBuilderEvent( |
| 349 | DisplayBuilderEvents::ON_UPDATE, |
| 350 | NULL, |
| 351 | $node_id, |
| 352 | NULL, |
| 353 | ); |
| 354 | } |
| 359 | public function undo(Request $request, InstanceInterface $display_builder_instance): array { |
| 360 | /** @var \Drupal\display_builder\Entity\InstanceStorage $storage */ |
| 361 | $storage = $this->entityTypeManager()->getStorage('display_builder_instance'); |
| 362 | /** @var \Drupal\display_builder\InstanceInterface $display_builder_instance */ |
| 363 | $display_builder_instance = $storage->undo($display_builder_instance); |
| 364 | |
| 365 | $this->builder = $display_builder_instance; |
| 366 | |
| 367 | return $this->dispatchDisplayBuilderEvent(DisplayBuilderEvents::ON_HISTORY_CHANGE); |
| 368 | } |
| 218 | public function update(Request $request, InstanceInterface $display_builder_instance, string $node_id): array { |
| 219 | $this->builder = $display_builder_instance; |
| 220 | $body = $request->getPayload()->all(); |
| 221 | |
| 222 | if (!isset($body['form_id'])) { |
| 223 | $message = $this->t('[update] Missing payload!'); |
| 224 | $debug = [ |
| 225 | 'node_id' => $node_id, |
| 226 | 'request' => $request->request->all(), |
| 227 | 'body' => $body, |
| 228 | 'instance' => $display_builder_instance, |
| 229 | ]; |
| 230 | |
| 231 | return $this->responseMessageError((string) $display_builder_instance->id(), $message, $debug); |
| 235 | $node = $display_builder_instance->getNode($node_id); |
| 236 | |
| 237 | if (isset($body['source']['form_build_id'])) { |
| 238 | unset($body['source']['form_build_id'], $body['source']['form_token'], $body['source']['form_id']); |
| 239 | } |
| 240 | |
| 241 | if (isset($body['form_build_id'])) { |
| 241 | if (isset($body['form_build_id'])) { |
| 242 | unset($body['form_build_id'], $body['form_token'], $body['form_id']); |
| 243 | } |
| 244 | $form_state = new FormState(); |
| 244 | $form_state = new FormState(); |
| 245 | // Default values are the existing values from the state. |
| 246 | $form_state->addBuildInfo('args', [ |
| 247 | [ |
| 248 | 'island_id' => 'contextual_form', |
| 249 | 'builder_id' => (string) $display_builder_instance->id(), |
| 250 | 'instance' => $node, |
| 251 | ], |
| 252 | $display_builder_instance->getAvailableContexts(), |
| 253 | ]); |
| 254 | $form_state->setTemporaryValue('gathered_contexts', $display_builder_instance->getAvailableContexts()); |
| 255 | // The body received corresponds to raw form values. |
| 256 | // We need to set them in the form state to properly |
| 257 | // take them into account. |
| 258 | $form_state->setValues($body); |
| 259 | |
| 260 | $formClass = ContextualFormPanel::getFormClass(); |
| 261 | $data = []; |
| 262 | |
| 263 | try { |
| 264 | $values = $this->validateIslandForm($formClass, $form_state); |
| 265 | $data['source'] = $values; |
| 267 | catch (FormAjaxException $e) { |
| 268 | throw $e; |
| 270 | catch (\Exception $e) { |
| 272 | 'node_id' => $node_id, |
| 273 | 'request' => $request->request->all(), |
| 274 | 'form' => $form_state->getValues(), |
| 275 | 'body' => $body, |
| 276 | 'instance' => $display_builder_instance, |
| 277 | ]; |
| 278 | |
| 279 | return $this->responseMessageError((string) $display_builder_instance->id(), $e->getMessage(), $debug); |
| 282 | $display_builder_instance->setSource($node_id, $node['source_id'], $data['source']); |
| 283 | $display_builder_instance->save(); |
| 284 | |
| 285 | $this->builder = $display_builder_instance; |
| 286 | $this->islandId = (string) $request->query->get('from', NULL); |
| 287 | |
| 288 | return $this->dispatchDisplayBuilderEvent( |
| 289 | DisplayBuilderEvents::ON_UPDATE, |
| 290 | NULL, |
| 291 | $node_id, |
| 292 | ); |
| 293 | } |
| 543 | private function validateIslandForm(string $formClass, FormStateInterface $form_state): array { |
| 544 | /** @var \Drupal\Core\Form\FormBuilder $formBuilder */ |
| 545 | $formBuilder = $this->formBuilder(); |
| 546 | |
| 547 | try { |
| 548 | $triggering_element = $form_state->getTriggeringElement(); |
| 549 | |
| 550 | if (!$triggering_element && !isset($form_state->getValues()['_triggering_element_name'])) { |
| 550 | if (!$triggering_element && !isset($form_state->getValues()['_triggering_element_name'])) { |
| 550 | if (!$triggering_element && !isset($form_state->getValues()['_triggering_element_name'])) { |
| 552 | $form_state->setTriggeringElement([ |
| 553 | '#type' => 'submit', |
| 554 | '#limit_validation_errors' => FALSE, |
| 555 | '#value' => (string) $this->t('Submit'), |
| 556 | ]); |
| 557 | } |
| 558 | $form = $formBuilder->buildForm($formClass, $form_state); |
| 558 | $form = $formBuilder->buildForm($formClass, $form_state); |
| 559 | $formErrors = $form_state->getErrors(); |
| 560 | |
| 561 | if (!empty($formErrors)) { |
| 562 | $first_error = \reset($formErrors); |
| 563 | |
| 564 | throw new FormValidationException((string) $first_error); |
| 566 | $formBuilder->validateForm($formClass, $form, $form_state); |
| 567 | $formErrors = $form_state->getErrors(); |
| 568 | |
| 569 | if (!empty($formErrors)) { |
| 570 | $first_error = \reset($formErrors); |
| 571 | |
| 572 | throw new FormValidationException((string) $first_error); |
| 572 | throw new FormValidationException((string) $first_error); |
| 575 | catch (FormAjaxException $e) { |
| 576 | throw $e; |
| 580 | $values = $form_state->getValues(); |
| 581 | |
| 582 | // We clean the values from form API keys. |
| 583 | if (isset($values['form_build_id'])) { |
| 584 | unset($values['form_build_id'], $values['form_token'], $values['form_id']); |
| 585 | } |
| 586 | |
| 587 | return $values; |
| 587 | return $values; |
| 588 | } |