Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • 2.0.x
  • 8.x-1.x
  • 2.0.0
  • 2.0.0-alpha1
  • 2.0.0-alpha2
  • 2.0.0-alpha3
  • 2.0.0-beta1
  • 2.0.0-beta2
  • 2.0.0-beta3
  • 2.0.0-beta4
  • 2.0.0-beta5
  • 2.0.0-beta6
  • 2.0.0-rc1
  • 2.0.0-rc2
  • 2.0.1
  • 2.0.2
  • 2.0.3
  • 8.x-1.0
  • 8.x-1.0-beta1
  • 8.x-1.0-beta2
  • 8.x-1.0-beta3
  • 8.x-1.0-beta4
  • 8.x-1.0-beta5
  • 8.x-1.0-beta6
  • 8.x-1.0-beta7
  • 8.x-1.0-rc1
  • 8.x-1.0-rc2
  • 8.x-1.1
  • 8.x-1.10
  • 8.x-1.2
  • 8.x-1.3
  • 8.x-1.4
  • 8.x-1.5
  • 8.x-1.6
  • 8.x-1.7
  • 8.x-1.8
  • 8.x-1.9
  • previous/3395953-2.0.x-add-new/2024-01-11
  • previous/3395960-2.0.x-add-new/2024-01-30
  • previous/3395960-2.0.x-add-new/2024-01-30-1
  • previous/3395960-2.0.x-add-new/2024-01-30-2
  • previous/3395960-2.0.x-test-new-context/2024-04-05-2
  • previous/3413589-2.0.0-beta1-add-alterinfocomponentinfo/2024-07-22
  • previous/3414291-2.0.0-alpha1-source-pluginsmethods/2024-02-24
  • previous/3414291-2.0.0-alpha1-source-pluginsmethods/2024-02-24-1
  • previous/3423185-2.0.0-alpha3-add-allowexpose-derivable_contexts/2024-06-24-2
  • previous/3423185-2.0.0-alpha3-add-allowexpose/2024-06-09
  • previous/3423185-2.0.0-alpha3-add-allowexpose/2024-06-15
  • previous/3423185-2.0.0-alpha3-add-allowexpose/2024-06-20
  • previous/3423185-2.0.0-alpha3-add-allowexpose/2024-06-20-1
  • previous/3423185-2.0.0-alpha3-add-allowexpose/2024-06-21
  • previous/3443441-one-block-per-component/2024-05-27
  • previous/3444770-2.0.0-beta1-componentformbuilder-make/2024-07-26
  • previous/3444770-2.0.0-beta1-componentformbuilder-make/2024-08-05
  • previous/3461566-2.0.0-beta2-ux-slots/2024-09-09-1
  • previous/3469814-2.0.0-beta2-kernel-source/2024-09-05
  • previous/3470386--2.0.0-beta2-render/2024-08-26
57 results

Target

Select target project
  • project/ui_patterns
  • issue/ui_patterns-3290197
  • issue/ui_patterns-3311088
  • issue/ui_patterns-3311340
  • issue/ui_patterns-3311342
  • issue/ui_patterns-3311471
  • issue/ui_patterns-3314293
  • issue/ui_patterns-3313689
  • issue/ui_patterns-3314463
  • issue/ui_patterns-3318269
  • issue/ui_patterns-3314998
  • issue/ui_patterns-3316487
  • issue/ui_patterns-3311455
  • issue/ui_patterns-3314789
  • issue/ui_patterns-3326826
  • issue/ui_patterns-3315661
  • issue/ui_patterns-3311452
  • issue/ui_patterns-3315533
  • issue/ui_patterns-3315660
  • issue/ui_patterns-3311464
  • issue/ui_patterns-3328272
  • issue/ui_patterns-3333738
  • issue/ui_patterns-3342949
  • issue/ui_patterns-3335833
  • issue/ui_patterns-3341073
  • issue/ui_patterns-3311480
  • issue/ui_patterns-3348852
  • issue/ui_patterns-3342390
  • issue/ui_patterns-3349076
  • issue/ui_patterns-3349077
  • issue/ui_patterns-3349079
  • issue/ui_patterns-3353287
  • issue/ui_patterns-3353289
  • issue/ui_patterns-3347891
  • issue/ui_patterns-3351682
  • issue/ui_patterns-3336970
  • issue/ui_patterns-3406383
  • issue/ui_patterns-3371032
  • issue/ui_patterns-3369867
  • issue/ui_patterns-3377457
  • issue/ui_patterns-3381188
  • issue/ui_patterns-3383762
  • issue/ui_patterns-3344405
  • issue/ui_patterns-3395953
  • issue/ui_patterns-3395963
  • issue/ui_patterns-3395960
  • issue/ui_patterns-3410258
  • issue/ui_patterns-3424696
  • issue/ui_patterns-3441490
  • issue/ui_patterns-3413043
  • issue/ui_patterns-3413703
  • issue/ui_patterns-3414116
  • issue/ui_patterns-3414363
  • issue/ui_patterns-3414293
  • issue/ui_patterns-3414774
  • issue/ui_patterns-3417517
  • issue/ui_patterns-3382718
  • issue/ui_patterns-3420517
  • issue/ui_patterns-3420628
  • issue/ui_patterns-3420653
  • issue/ui_patterns-3421484
  • issue/ui_patterns-3421688
  • issue/ui_patterns-3414291
  • issue/ui_patterns-3438996
  • issue/ui_patterns-3438998
  • issue/ui_patterns-3427538
  • issue/ui_patterns-3441326
  • issue/ui_patterns-3446471
  • issue/ui_patterns-3438952
  • issue/ui_patterns-3438432
  • issue/ui_patterns-3437218
  • issue/ui_patterns-3435188
  • issue/ui_patterns-3435827
  • issue/ui_patterns-3441484
  • issue/ui_patterns-3439414
  • issue/ui_patterns-3440202
  • issue/ui_patterns-3440319
  • issue/ui_patterns-3440310
  • issue/ui_patterns-3440278
  • issue/ui_patterns-3438949
  • issue/ui_patterns-3395959
  • issue/ui_patterns-3441987
  • issue/ui_patterns-3440441
  • issue/ui_patterns-3439407
  • issue/ui_patterns-3444348
  • issue/ui_patterns-3437250
  • issue/ui_patterns-3445079
  • issue/ui_patterns-3447833
  • issue/ui_patterns-3443441
  • issue/ui_patterns-3444716
  • issue/ui_patterns-3449932
  • issue/ui_patterns-3444711
  • issue/ui_patterns-3444822
  • issue/ui_patterns-3447985
  • issue/ui_patterns-3452751
  • issue/ui_patterns-3450015
  • issue/ui_patterns-3423185
  • issue/ui_patterns-3444768
  • issue/ui_patterns-3447902
  • issue/ui_patterns-3453793
  • issue/ui_patterns-3454499
  • issue/ui_patterns-3454615
  • issue/ui_patterns-3455076
  • issue/ui_patterns-3455074
  • issue/ui_patterns-3395954
  • issue/ui_patterns-3360373
  • issue/ui_patterns-3438360
  • issue/ui_patterns-3455951
  • issue/ui_patterns-3456083
  • issue/ui_patterns-3456295
  • issue/ui_patterns-3456335
  • issue/ui_patterns-3456484
  • issue/ui_patterns-3457273
  • issue/ui_patterns-3457505
  • issue/ui_patterns-3457620
  • issue/ui_patterns-3456571
  • issue/ui_patterns-3458364
  • issue/ui_patterns-3413609
  • issue/ui_patterns-3459187
  • issue/ui_patterns-3459271
  • issue/ui_patterns-3413589
  • issue/ui_patterns-3459741
  • issue/ui_patterns-3459113
  • issue/ui_patterns-3462312
  • issue/ui_patterns-3462329
  • issue/ui_patterns-3452481
  • issue/ui_patterns-3462505
  • issue/ui_patterns-3460800
  • issue/ui_patterns-3460784
  • issue/ui_patterns-3461858
  • issue/ui_patterns-3462504
  • issue/ui_patterns-3444770
  • issue/ui_patterns-3461579
  • issue/ui_patterns-3461274
  • issue/ui_patterns-3461277
  • issue/ui_patterns-3462818
  • issue/ui_patterns-3464287
  • issue/ui_patterns-3464288
  • issue/ui_patterns-3464291
  • issue/ui_patterns-3464290
  • issue/ui_patterns-3464289
  • issue/ui_patterns-3464879
  • issue/ui_patterns-3441232
  • issue/ui_patterns-3465262
  • issue/ui_patterns-3465264
  • issue/ui_patterns-3464894
  • issue/ui_patterns-3465432
  • issue/ui_patterns-3465453
  • issue/ui_patterns-3465497
  • issue/ui_patterns-3465517
  • issue/ui_patterns-3465488
  • issue/ui_patterns-3465524
  • issue/ui_patterns-3465764
  • issue/ui_patterns-3465821
  • issue/ui_patterns-3465823
  • issue/ui_patterns-3463153
  • issue/ui_patterns-3466385
  • issue/ui_patterns-3466495
  • issue/ui_patterns-3466381
  • issue/ui_patterns-3464630
  • issue/ui_patterns-3466958
  • issue/ui_patterns-3467219
  • issue/ui_patterns-3467398
  • issue/ui_patterns-3467411
  • issue/ui_patterns-3467502
  • issue/ui_patterns-3467653
  • issue/ui_patterns-3461566
  • issue/ui_patterns-3467825
  • issue/ui_patterns-3466359
  • issue/ui_patterns-3466353
  • issue/ui_patterns-3469814
  • issue/ui_patterns-3469665
  • issue/ui_patterns-3470386
  • issue/ui_patterns-3470231
  • issue/ui_patterns-3469808
  • issue/ui_patterns-3471426
  • issue/ui_patterns-3471587
  • issue/ui_patterns-3462146
  • issue/ui_patterns-3455354
  • issue/ui_patterns-3473038
  • issue/ui_patterns-3472571
  • issue/ui_patterns-3467655
  • issue/ui_patterns-3467652
  • issue/ui_patterns-3473344
  • issue/ui_patterns-3473372
  • issue/ui_patterns-3473393
  • issue/ui_patterns-3473412
  • issue/ui_patterns-3462737
  • issue/ui_patterns-3473021
  • issue/ui_patterns-3473621
  • issue/ui_patterns-3456072
  • issue/ui_patterns-3469805
  • issue/ui_patterns-3473804
  • issue/ui_patterns-3473346
  • issue/ui_patterns-3464869
  • issue/ui_patterns-3474113
  • issue/ui_patterns-3474140
  • issue/ui_patterns-3474141
  • issue/ui_patterns-3474137
  • issue/ui_patterns-3474892
  • issue/ui_patterns-3475282
  • issue/ui_patterns-3475564
  • issue/ui_patterns-3475574
  • issue/ui_patterns-3474822
  • issue/ui_patterns-3475857
  • issue/ui_patterns-3474641
  • issue/ui_patterns-3473852
  • issue/ui_patterns-3475163
  • issue/ui_patterns-3467657
  • issue/ui_patterns-3475962
  • issue/ui_patterns-3476246
  • issue/ui_patterns-3476469
  • issue/ui_patterns-3440293
  • issue/ui_patterns-3477106
  • issue/ui_patterns-3477296
  • issue/ui_patterns-3477573
  • issue/ui_patterns-3477625
  • issue/ui_patterns-3467656
  • issue/ui_patterns-3477702
  • issue/ui_patterns-3477419
  • issue/ui_patterns-3478622
  • issue/ui_patterns-3478655
  • issue/ui_patterns-3478648
  • issue/ui_patterns-3479095
  • issue/ui_patterns-3477287
  • issue/ui_patterns-3479850
  • issue/ui_patterns-3480474
  • issue/ui_patterns-3479656
  • issue/ui_patterns-3480553
  • issue/ui_patterns-3478657
  • issue/ui_patterns-3481051
  • issue/ui_patterns-3481405
  • issue/ui_patterns-3481479
  • issue/ui_patterns-3481896
  • issue/ui_patterns-3482208
  • issue/ui_patterns-3483508
  • issue/ui_patterns-3483939
  • issue/ui_patterns-3483489
  • issue/ui_patterns-3485290
  • issue/ui_patterns-3485497
  • issue/ui_patterns-3485900
  • issue/ui_patterns-3486050
  • issue/ui_patterns-3485895
  • issue/ui_patterns-3486547
  • issue/ui_patterns-3483496
  • issue/ui_patterns-3462325
  • issue/ui_patterns-3383544
  • issue/ui_patterns-3449653
  • issue/ui_patterns-3487819
  • issue/ui_patterns-3484830
  • issue/ui_patterns-3488044
  • issue/ui_patterns-3488166
  • issue/ui_patterns-3488167
  • issue/ui_patterns-3488582
  • issue/ui_patterns-3477890
  • issue/ui_patterns-3481540
  • issue/ui_patterns-3488879
  • issue/ui_patterns-3490387
  • issue/ui_patterns-3489973
  • issue/ui_patterns-3490599
  • issue/ui_patterns-3490615
  • issue/ui_patterns-3490696
  • issue/ui_patterns-3490733
  • issue/ui_patterns-3473889
  • issue/ui_patterns-3491238
  • issue/ui_patterns-3491369
  • issue/ui_patterns-3491536
  • issue/ui_patterns-3491560
  • issue/ui_patterns-3487818
  • issue/ui_patterns-3492211
  • issue/ui_patterns-3474511
  • issue/ui_patterns-3437220
  • issue/ui_patterns-3481491
  • issue/ui_patterns-3490872
  • issue/ui_patterns-3492962
  • issue/ui_patterns-3483831
  • issue/ui_patterns-3494404
  • issue/ui_patterns-3494336
  • issue/ui_patterns-3472993
  • issue/ui_patterns-3490873
  • issue/ui_patterns-3494794
  • issue/ui_patterns-3494338
  • issue/ui_patterns-3490476
  • issue/ui_patterns-3494949
  • issue/ui_patterns-3491705
  • issue/ui_patterns-3490150
  • issue/ui_patterns-3487548
  • issue/ui_patterns-3493134
  • issue/ui_patterns-3495847
  • issue/ui_patterns-3495982
  • issue/ui_patterns-3498128
  • issue/ui_patterns-3498184
  • issue/ui_patterns-3496114
  • issue/ui_patterns-3496111
  • issue/ui_patterns-3497683
  • issue/ui_patterns-3498428
  • issue/ui_patterns-3499625
  • issue/ui_patterns-3498456
  • issue/ui_patterns-3495984
  • issue/ui_patterns-3498137
  • issue/ui_patterns-3500243
  • issue/ui_patterns-3499983
  • issue/ui_patterns-3500669
  • issue/ui_patterns-3500244
  • issue/ui_patterns-3498496
  • issue/ui_patterns-3501430
  • issue/ui_patterns-3501400
  • issue/ui_patterns-3501914
  • issue/ui_patterns-3502069
  • issue/ui_patterns-3502079
  • issue/ui_patterns-3502916
  • issue/ui_patterns-3503141
  • issue/ui_patterns-3504119
  • issue/ui_patterns-3504351
  • issue/ui_patterns-3504811
  • issue/ui_patterns-3504857
  • issue/ui_patterns-3506138
  • issue/ui_patterns-3506145
  • issue/ui_patterns-3508054
  • issue/ui_patterns-3507007
  • issue/ui_patterns-3508463
  • issue/ui_patterns-3510560
  • issue/ui_patterns-3510596
  • issue/ui_patterns-3511027
  • issue/ui_patterns-3511112
  • issue/ui_patterns-3511203
  • issue/ui_patterns-3512018
  • issue/ui_patterns-3511397
  • issue/ui_patterns-3512477
  • issue/ui_patterns-3511351
  • issue/ui_patterns-3513456
  • issue/ui_patterns-3513546
  • issue/ui_patterns-3513568
  • issue/ui_patterns-3513653
  • issue/ui_patterns-3514270
  • issue/ui_patterns-3514800
  • issue/ui_patterns-3514886
  • issue/ui_patterns-3488418
  • issue/ui_patterns-3515500
  • issue/ui_patterns-3516308
  • issue/ui_patterns-3516369
  • issue/ui_patterns-3508748
  • issue/ui_patterns-3517343
  • issue/ui_patterns-3517621
  • issue/ui_patterns-3517724
  • issue/ui_patterns-3517695
  • issue/ui_patterns-3518099
  • issue/ui_patterns-3519282
  • issue/ui_patterns-3519517
  • issue/ui_patterns-3519543
  • issue/ui_patterns-3519934
351 results
Select Git revision
  • 2.0.x
  • 3510596-boolean-prop-with
  • 3510596-improved
  • 8.x-1.x
  • 2.0.0
  • 2.0.0-alpha1
  • 2.0.0-alpha2
  • 2.0.0-alpha3
  • 2.0.0-beta1
  • 2.0.0-beta2
  • 2.0.0-beta3
  • 2.0.0-beta4
  • 2.0.0-beta5
  • 2.0.0-beta6
  • 2.0.0-rc1
  • 2.0.0-rc2
  • 8.x-1.0
  • 8.x-1.0-beta1
  • 8.x-1.0-beta2
  • 8.x-1.0-beta3
  • 8.x-1.0-beta4
  • 8.x-1.0-beta5
  • 8.x-1.0-beta6
  • 8.x-1.0-beta7
  • 8.x-1.0-rc1
  • 8.x-1.0-rc2
  • 8.x-1.1
  • 8.x-1.10
  • 8.x-1.2
  • 8.x-1.3
  • 8.x-1.4
  • 8.x-1.5
  • 8.x-1.6
  • 8.x-1.7
  • 8.x-1.8
  • 8.x-1.9
  • previous/3510596-boolean-prop-with/2025-03-12
  • previous/3510596-boolean-prop-with/2025-03-20
  • previous/3510596-improved/2025-03-24
39 results
Show changes
Commits on Source (9)
Showing
with 2777 additions and 134 deletions
<?php
namespace Drupal\ui_patterns_field\Plugin\Derivative;
use Drupal\Component\Plugin\PluginBase;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\ui_patterns\Plugin\Derivative\EntityFieldSourceDeriverBase;
/**
* Provides Plugin for every field property of type ui_patterns_source.
*/
class UIPatternsSourceFieldPropertySourceDeriver extends EntityFieldSourceDeriverBase {
/**
* {@inheritdoc}
*/
protected function getDerivativeDefinitionsForEntityStorageField(string $entity_type_id, string $field_name, array $base_plugin_derivative): void {
$id = implode(PluginBase::DERIVATIVE_SEPARATOR, [
$entity_type_id,
$field_name,
]);
$field_type = $this->entityFieldsMetadata[$entity_type_id]["field_storages"][$field_name]["metadata"]["type"];
if ($field_type === "ui_patterns_source") {
$this->derivatives[$id] = array_merge(
$base_plugin_derivative,
[
"id" => $id,
"tags" => array_merge($base_plugin_derivative["tags"], ["ui_patterns_source"]),
]);
$field_storage_data = $this->entityFieldsMetadata[$entity_type_id]["field_storages"][$field_name];
$bundle_context_for_properties = (new ContextDefinition('string'))
->setRequired()
->setLabel("Bundle")
->addConstraint('AllowedValues', array_merge($field_storage_data["bundles"] ?? [], [""]));
$this->derivatives[$id]["context_definitions"]["bundle"] = $bundle_context_for_properties;
}
}
}
<?php
namespace Drupal\ui_patterns_field\Plugin\Field\FieldType;
use Drupal\Core\Field\Attribute\FieldType;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\MapFieldItemList;
use Drupal\Core\Field\Plugin\Field\FieldType\MapItem;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
* Field Type to store UI Patterns source configuration.
*
* @property string $source_id
* @property string $source
*/
#[FieldType(
id: "ui_patterns_source",
label: new TranslatableMarkup("Source (UI Patterns)"),
description: new TranslatableMarkup("Store an UI Patterns source configuration"),
default_widget: "ui_patterns_source",
default_formatter: "ui_patterns_source",
list_class: MapFieldItemList::class,
)]
class SourceValueItem extends MapItem {
/**
* {@inheritdoc}
*/
public static function mainPropertyName() {
return 'source_id';
}
/**
* {@inheritdoc}
*/
public static function schema(FieldStorageDefinitionInterface $field_definition) {
return [
'columns' => [
'source_id' => [
'type' => 'varchar_ascii',
'length' => 255,
],
'source' => [
'type' => 'blob',
'size' => 'big',
'serialize' => TRUE,
],
],
];
}
}
<?php
namespace Drupal\ui_patterns_field\Plugin\Field\FieldWidget;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Field\Attribute\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\Core\Plugin\Context\EntityContext;
use Drupal\Core\Render\Component\Exception\ComponentNotFoundException;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Theme\ComponentPluginManager;
use Drupal\ui_patterns\Form\ComponentFormBuilderTrait;
use Drupal\ui_patterns\Plugin\Context\RequirementsContext;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\ui_patterns\ComponentPluginManager as UIPatternsComponentPluginManager;
/**
* A widget to display the UI Patterns configuration form.
*
* @internal
* Plugin classes are internal.
*/
#[FieldWidget(
id: 'ui_patterns_source_component',
label: new TranslatableMarkup('Components only (UI Patterns)'),
description: new TranslatableMarkup('Widget to edit an UI Patterns source field, but configure only the Component source for a slot.'),
field_types: ['ui_patterns_source'],
)]
class SourceComponentWidget extends WidgetBase {
use ComponentFormBuilderTrait;
/**
* The component plugin manager.
*
* @var \Drupal\Core\Theme\ComponentPluginManager
*/
protected ComponentPluginManager $componentPluginManager;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
$instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
$instance->componentPluginManager = $container->get('plugin.manager.sdc');
return $instance;
}
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
'component_id' => NULL,
'hide_slots' => TRUE,
'prop_sources' => NULL,
'prop_filter_enable' => FALSE,
'allow_override' => FALSE,
'selection' => [],
] + parent::defaultSettings();
}
/**
* Get the widget settings.
*
* @param \Drupal\Core\Form\FormStateInterface $form_state
* Form state.
*
* @return array
* Widget settings.
*/
protected function getWidgetSettings(FormStateInterface $form_state) : array {
$field_name = $this->fieldDefinition->getName();
$array_parents = ["fields", $field_name, "settings_edit_form", "settings"];
$full_form_state_values = $form_state->getValues();
$current_settings = &NestedArray::getValue($full_form_state_values, $array_parents);
return array_merge($this->getSettings(), $current_settings ?? []);
}
/**
* Get the component options.
*
* @return array
* Component options.
*/
protected function getComponentOptions() : array {
$definitions = [];
if ($this->componentPluginManager instanceof UIPatternsComponentPluginManager) {
$definitions = $this->componentPluginManager->getGroupedDefinitions();
}
$options = [];
foreach ($definitions as $group_id => $group) {
foreach ($group as $component_id => $definition) {
$options[$group_id][$component_id] = $definition['annotated_name'];
}
}
return $options;
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$options = $this->getComponentOptions();
$field_name = $this->fieldDefinition->getName();
$settings = $this->getWidgetSettings($form_state);
$wrapper_id = 'component-props-selection';
$element = [];
$element['component_id'] = [
'#type' => 'select',
'#title' => $this->t('Component ID'),
'#default_value' => $this->getSetting('component_id'),
'#required' => FALSE,
'#options' => $options,
'#ajax' => [
'callback' => [static::class, 'changeSelectorFormChangeAjax'],
'wrapper' => $wrapper_id,
'effect' => 'fade',
],
'#executes_submit_callback' => FALSE,
'#empty_value' => '',
'#empty_option' => t('- None -'),
];
$element['allow_override'] = [
'#type' => 'checkbox',
'#title' => $this->t('Allow override of component form?'),
'#default_value' => $settings['allow_override'] ?? FALSE,
'#required' => FALSE,
];
$element['hide_slots'] = [
'#type' => 'checkbox',
'#title' => $this->t('Hide slots'),
'#default_value' => $settings['hide_slots'] ?? TRUE,
'#required' => FALSE,
];
$prop_sources = $settings['prop_sources'] ?? NULL;
if ($prop_sources === NULL) {
$prop_sources = '';
}
$element['prop_sources'] = [
'#type' => 'select',
'#title' => $this->t('Prop sources'),
'#options' => [
'' => $this->t('Display all'),
'widgets' => $this->t('Only widgets'),
'default' => $this->t('Only default'),
],
'#default_value' => $prop_sources,
'#required' => FALSE,
];
$element['prop_filter_enable'] = [
'#type' => 'checkbox',
'#title' => $this->t('Show only selected props'),
'#default_value' => $settings['prop_filter_enable'] ?? FALSE,
'#required' => FALSE,
'#states' => [
'visible' => [
[
":input[name='fields[{$field_name}][settings_edit_form][settings][component_id]']" => ['!value' => ''],
],
],
],
];
$element["selection"] = [
"#type" => "container",
"#attributes" => [
"id" => $wrapper_id,
],
"#tree" => TRUE,
];
$component_id_selected = $settings["component_id"] ?? '';
if (!empty($component_id_selected)) {
$selection = $settings["selection"] ?? [];
try {
$component_selected = $this->componentPluginManager->find($component_id_selected);
$props = $component_selected->metadata->schema['properties'];
$options = ["variant" => t("Variant")];
foreach ($props as $prop_id => $prop) {
if ($prop_id === 'variant') {
continue;
}
$propTitle = $prop['title'] ?? '';
$options[$prop_id] = empty($propTitle) ? $prop_id : $propTitle;
}
$element["selection"]['prop_filter'] = [
"#type" => "select",
'#limit_validation_errors' => [],
"#multiple" => TRUE,
"#options" => $options,
"#default_value" => $selection['prop_filter'] ?? [],
'#states' => [
'visible' => [
[
":input[name='fields[{$field_name}][settings_edit_form][settings][prop_filter_enable]']" => ['checked' => TRUE],
":input[name='fields[{$field_name}][settings_edit_form][settings][component_id]']" => ['!value' => ''],
],
],
],
];
}
catch (ComponentNotFoundException $e) {
}
}
return $element;
}
/**
* Ajax callback for component selector change.
*/
public static function changeSelectorFormChangeAjax(
array $form,
FormStateInterface $form_state,
) : array {
$parents = $form_state->getTriggeringElement()['#array_parents'];
$sub_form_parents = array_merge(array_slice($parents, 0, -1), ["selection"]);
$sub_form = NestedArray::getValue($form, $sub_form_parents);
$form_state->setRebuild();
return $sub_form;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = [];
if ($this->getSetting('component_id') !== NULL) {
$summary[] = $this->t('Component Id: @component', ['@component' => $this->getSetting('component_id')]);
}
if (!($this->getSetting('hide_slots') ?? TRUE)) {
$summary[] = $this->t('Hide slots');
}
$selection = $this->getSetting('prop_filter_enable') ?? [];
$props_selection = $selection['props'] ?? NULL;
if (is_array($props_selection)) {
$summary[] = $this->t('Only selected props: @props', ['@props' => implode(",", $props_selection)]);
}
return $summary;
}
/**
* {@inheritdoc}
*/
public function form(FieldItemListInterface $items, array &$form, FormStateInterface $form_state, $get_delta = NULL) {
$field_name = $this->fieldDefinition->getName();
$parents = $form['#parents'];
if (!static::getWidgetState($parents, $field_name, $form_state)) {
$field_state = [
'items_count' => count($items) - 1,
'array_parents' => [],
];
static::setWidgetState($parents, $field_name, $form_state, $field_state);
}
return parent::form($items, $form, $form_state, $get_delta);
}
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$item_delta_value = $items[$delta]->getValue() ?? [];
$source_id = $item_delta_value['source_id'] ?? 'component';
$field_name = $this->fieldDefinition->getName();
$element['#parents'] = array_merge($element['#field_parents'] ?? [], [$field_name, $delta]);
$settings = $this->getSettings() ?? [];
$component_id = $settings['component_id'] ?? NULL;
if (empty($component_id)) {
$component_id = NULL;
}
if ($source_id !== 'component') {
// The widget can only deal with component sources.
// To make sure no data will be overwritten we disable the widget.
$element['#access'] = FALSE;
return $element;
}
$source_data = $item_delta_value["source"] ?? [];
$component_default_value = $source_data['component'] ?? [];
$component_in_data = $component_default_value['component_id'] ?? NULL;
if (!isset($component_default_value['component_id'])) {
$component_default_value['component_id'] = $component_id;
}
elseif ($component_id && $component_in_data && $component_in_data !== $component_id) {
$element['#access'] = FALSE;
return $element;
}
$contexts = $this->getComponentSourceContexts($items);
$element['source'] = [
'#type' => 'container',
'#tree' => TRUE,
];
$selection = $settings["selection"] ?? [];
$prop_sources = $settings['prop_sources'] ?? '';
$wrap = ($prop_sources !== 'default');
$hide_slots = $settings['hide_slots'] ?? TRUE;
$form_element_overrides = [
'#allow_override' => $settings['allow_override'] ?? FALSE,
'#tag_filter' => ((bool) ($settings['only_widgets'] ?? TRUE)) ? ["widget" => TRUE] : [],
'#default_value' => $component_default_value,
'#wrap' => $wrap,
'#render_headings' => !$hide_slots,
'#render_sources' => $wrap,
'#prop_filter' => ($this->getSetting('prop_filter_enable') ?? FALSE) ? $selection['prop_filter'] ?? NULL : NULL,
];
$element['source']["component"] = $this->buildComponentsForm($form_state, $contexts, $component_id, !$hide_slots, TRUE, 'ui_patterns', $form_element_overrides);
$element['source_id'] = ['#type' => 'hidden', '#value' => $source_id];
return $element;
}
/**
* Set the context.
*
* @param \Drupal\Core\Field\FieldItemListInterface|null $items
* Field items.
*
* @return array
* Source contexts.
*/
protected function getComponentSourceContexts(?FieldItemListInterface $items = NULL): array {
$contexts = [];
if ($entity = $items?->getEntity()) {
$contexts['entity'] = EntityContext::fromEntity($entity);
$contexts['bundle'] = new Context(ContextDefinition::create('string'), $contexts["entity"]->getContextValue()->bundle() ?? "");
}
$contexts = RequirementsContext::addToContext(["field_granularity:item"], $contexts);
return $contexts;
}
}
<?php
namespace Drupal\ui_patterns_field\Plugin\Field\FieldWidget;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Field\Attribute\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\Core\Plugin\Context\EntityContext;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\ui_patterns\Element\ComponentSlotForm;
use Drupal\ui_patterns\Plugin\Context\RequirementsContext;
use Drupal\ui_patterns\SourcePluginManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* A widget to display the UI Patterns configuration form.
*
* @internal
* Plugin classes are internal.
*/
#[FieldWidget(
id: 'ui_patterns_source',
label: new TranslatableMarkup('All slot sources (UI Patterns)'),
description: new TranslatableMarkup('Widget to edit an UI Patterns source field and configure any source for a slot.'),
field_types: ['ui_patterns_source'],
)]
class SourceWidget extends WidgetBase {
/**
* The source plugin manager.
*
* @var \Drupal\ui_patterns\SourcePluginManager
*/
protected SourcePluginManager $sourcePluginManager;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
$instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
$instance->sourcePluginManager = $container->get('plugin.manager.ui_patterns_source');
return $instance;
}
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$field_name = $this->fieldDefinition->getName();
$element['#slot_id'] = $delta;
$element['#parents'] = array_merge($element['#field_parents'] ?? [], [$field_name, $delta]);
$default_value = $this->getDefaultValue($items, $delta, $element, $form, $form_state);
$element['#source_contexts'] = $this->getComponentSourceContexts($items);
$element['#tag_filter'] = $this->getSetting('tag_filter') ?? [];
$source_form = ComponentSlotForm::buildSourceForm($element, $form_state, [], $default_value);
$source_form['source_id']['#empty_option'] = t("- Select a source to add -");
return $element + $source_form;
}
/**
* Returns the default value for the source field.
*
* @param \Drupal\Core\Field\FieldItemListInterface $items
* The field items.
* @param int $delta
* The delta.
* @param array $element
* The element.
* @param array $form
* The form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*
* @return mixed
* The default value.
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
protected function getDefaultValue(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) : mixed {
$full_form_state_values = $form_state->getValues() ?? [];
$full_input = $form_state->getUserInput();
$form_state_values = &NestedArray::getValue($full_form_state_values, $element["#parents"] ?? []);
if (!empty($full_input) || $form_state->isProcessingInput() || $form_state->isRebuilding()) {
$form_state_values = &NestedArray::getValue($full_input, $element["#parents"] ?? []);
}
$default_value = array_merge($items[$delta]?->getValue() ?? [], $form_state_values ?? []);
return $default_value;
}
/**
* Set the context.
*
* @param \Drupal\Core\Field\FieldItemListInterface|null $items
* Field items.
*
* @return array
* Source contexts.
*/
protected function getComponentSourceContexts(?FieldItemListInterface $items = NULL): array {
$contexts = [];
if ($entity = $items?->getEntity()) {
$contexts['entity'] = EntityContext::fromEntity($entity);
$contexts['bundle'] = new Context(ContextDefinition::create('string'), $contexts["entity"]->getContextValue()->bundle() ?? "");
}
$contexts = RequirementsContext::addToContext(["field_granularity:item"], $contexts);
return $contexts;
}
}
<?php
declare(strict_types=1);
namespace Drupal\ui_patterns_field\Plugin\UiPatterns\Source;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\ui_patterns\Attribute\Source;
use Drupal\ui_patterns\Plugin\UiPatterns\Source\FieldPropertySource;
use Drupal\ui_patterns_field\Plugin\Derivative\UIPatternsSourceFieldPropertySourceDeriver;
use Drupal\ui_patterns_field\Plugin\Field\FieldType\SourceValueItem;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Plugin implementation of the prop source.
*/
#[Source(
id: 'ui_patterns_source',
label: new TranslatableMarkup('Value from the component in field.'),
description: new TranslatableMarkup('Map the prop/slot value to the one configured in the component stored the "Source" field.'),
deriver: UIPatternsSourceFieldPropertySourceDeriver::class
)]
class UIPatternsSourceFieldPropertySource extends FieldPropertySource {
/**
* The component element builder.
*
* @var \Drupal\ui_patterns\Element\ComponentElementBuilder
*/
protected $componentElementBuilder;
/**
* {@inheritdoc}
*/
public static function create(
ContainerInterface $container,
array $configuration,
$plugin_id,
$plugin_definition,
) {
// We keep the same constructor as SourcePluginBase.
$instance = parent::create(
$container,
$configuration,
$plugin_id,
$plugin_definition
);
// Defined in parent class FieldSourceBase.
$instance->componentElementBuilder = $container->get('ui_patterns.component_element_builder');
return $instance;
}
/**
* {@inheritdoc}
*/
public function getPropValue(): mixed {
$items = $this->getEntityFieldItemList();
$delta = (isset($this->context['ui_patterns:field:index'])) ? $this->getContextValue('ui_patterns:field:index') : 0;
if (empty($items)) {
return NULL;
}
/** @var \Drupal\Core\Field\FieldItemInterface $field_item_at_delta */
$field_item_at_delta = $items->get($delta);
if (!$field_item_at_delta || !($field_item_at_delta instanceof SourceValueItem)) {
return NULL;
}
$source_id = $field_item_at_delta->source_id ?? 'component';
if ($source_id !== 'component') {
return NULL;
}
$source_configuration = $field_item_at_delta->source ?? [];
if (!is_array($source_configuration)) {
$source_configuration = [];
}
return $this->extractComponentPropValue($source_configuration);
}
/**
* Extract the prop value from the source configuration.
*
* @param array $source_configuration
* The source configuration.
*
* @return mixed
* The prop value.
*/
protected function extractComponentPropValue(array $source_configuration) : mixed {
$component_configuration = $source_configuration['component'] ?? [];
// $component_id = $component_configuration['component_id'] ?? NULL;
$propDefinition = $this->getPropDefinition();
$propId = $this->getPropId();
/** @var \Drupal\ui_patterns\PropTypeInterface $propType */
$propType = $propDefinition["ui_patterns"]["type_definition"];
$contexts = $this->getContexts();
$build = [];
if ($propType->getPluginId() === "slot") {
$sources = $component_configuration['slots'][$propId]["sources"] ?? [];
foreach ($sources as $source) {
$build = $this->componentElementBuilder->buildSource($build, $propId, $propDefinition, $source, $contexts);
}
return $build['#slots'][$propId] ?? [];
}
$build = [];
$prop_source_config = (($propId === "variant") && !empty($component_configuration["variant_id"])) ? $component_configuration["variant_id"] : ($component_configuration['props'][$propId] ?? []);
if (empty($prop_source_config)) {
return NULL;
}
$build = $this->componentElementBuilder->buildSource($build, $propId, $propDefinition, $prop_source_config, $contexts);
$property_value = $build['#props'][$propId] ?? NULL;
if (empty($property_value)) {
return NULL;
}
$prop_typ_types = [];
if (isset($this->propDefinition['type'])) {
// Type can be an array of types or a single type.
$prop_typ_types = is_array($this->propDefinition['type']) ? $this->propDefinition['type'] : [$this->propDefinition['type']];
}
return $this->transTypeProp($property_value, $prop_typ_types);
}
}
---
# this test demonstrates a simple mapping of a prop
# from a field to a component
# this fails if the field is not working (not saving)
# or if the mapping is not working
ui_patterns_source_mapping_simple_1:
component:
component_id: ui_patterns_test:test-component
props:
string:
source_id: entity_field
source:
derivable_context: 'field:node:page:field_ui_patterns_source_1'
'field:node:page:field_ui_patterns_source_1':
value:
source_id: 'ui_patterns_source:node:field_ui_patterns_source_1'
entity:
field_ui_patterns_source_1:
source_id: 'component'
source:
component:
component_id: 'ui_patterns_test:test-component'
props:
string:
source_id: 'textfield'
source:
value: 'value_text_1'
contexts:
field_name: 'field_ui_patterns_source_1'
output:
props:
string:
value: 'value_text_1'
<?php
declare(strict_types=1);
namespace Drupal\Tests\ui_patterns_field\Kernel\Source;
use Drupal\Tests\ui_patterns\Kernel\SourcePluginsTestBase;
/**
* Test UIPatternsSourceFieldPropertySource.
*
* @coversDefaultClass \Drupal\ui_patterns_field\Plugin\UiPatterns\Source\UIPatternsSourceFieldPropertySource
* @group ui_patterns_field
*/
class UIPatternsSourceFieldPropertySourceTest extends SourcePluginsTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['ui_patterns_field'];
/**
* Test Field Property Plugin.
*/
public function testPlugin(): void {
$testData = self::loadTestDataFixture(__DIR__ . "/../../../fixtures/tests.ui_patterns_source.yml");
$testSets = $testData->getTestSets();
foreach ($testSets as $test_set_name => $test_set) {
if (!str_starts_with($test_set_name, 'ui_patterns_source_')) {
continue;
}
$this->runSourcePluginTest($test_set);
}
}
}
name: UI Patterns Field
type: module
description: Use UI components and UI Patterns sources in fields.
core_version_requirement: ^10.3 || ^11
lifecycle: experimental
package: "User interface (experimental)"
dependencies:
- ui_patterns:ui_patterns
......@@ -4,14 +4,8 @@ declare(strict_types=1);
namespace Drupal\ui_patterns_field_formatters\Plugin\Field\FieldFormatter;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\Core\Plugin\Context\EntityContext;
use Drupal\Core\Theme\ComponentPluginManager;
use Drupal\ui_patterns\Form\ComponentSettingsFormBuilderTrait;
use Drupal\ui_patterns\Plugin\Context\RequirementsContext;
......@@ -32,69 +26,15 @@ abstract class ComponentFormatterBase extends FormatterBase {
*/
protected ComponentPluginManager $componentPluginManager;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The entity field manager.
*
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
*/
protected $entityFieldManager;
/**
* The sample entity generator.
*
* @var \Drupal\ui_patterns\Entity\SampleEntityGenerator
*/
protected $sampleEntityGenerator;
/**
* The chain context entity resolver.
*
* @var \Drupal\ui_patterns\Resolver\ContextEntityResolverInterface
*/
protected $chainContextEntityResolver;
/**
* The provided plugin contexts.
*
* @var array|null
*/
protected $context = NULL;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
$instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
$instance->componentPluginManager = $container->get('plugin.manager.sdc');
$instance->entityTypeManager = $container->get('entity_type.manager');
$instance->entityFieldManager = $container->get('entity_field.manager');
$instance->sampleEntityGenerator = $container->get('ui_patterns.sample_entity_generator');
$instance->chainContextEntityResolver = $container->get('ui_patterns.chain_context_entity_resolver');
return $instance;
}
/**
* Set the context.
*
* @param array $context
* Context.
*
* @return void
* Nothing.
*/
public function setContext(array $context): void {
$this->context = $context;
}
/**
* {@inheritdoc}
*/
......@@ -152,46 +92,6 @@ abstract class ComponentFormatterBase extends FormatterBase {
];
}
/**
* {@inheritdoc}
*/
protected function checkEntityHasField(EntityInterface $entity, string $entity_type_id, string $field_name) : bool {
$field_definitions = $this->entityFieldManager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle());
return ($entity->getEntityTypeId() === $entity_type_id &&
array_key_exists($field_name, $field_definitions));
}
/**
* Find an entity bundle which has a field.
*
* @param string $entity_type_id
* The entity type id.
* @param string $field_name
* The field name to be found in searched bundle.
*
* @return string
* The bundle.
*/
protected function findEntityBundleWithField(string $entity_type_id, string $field_name) : string {
// @todo better implementation with service 'entity_type.bundle.info'
$bundle = $entity_type_id;
$bundle_entity_type = $this->entityTypeManager->getDefinition($entity_type_id)->getBundleEntityType();
if (NULL !== $bundle_entity_type) {
$bundle_list = $this->entityTypeManager->getStorage($bundle_entity_type)->loadMultiple();
if (count($bundle_list) > 0) {
foreach ($bundle_list as $bundle_entity) {
$bundle_to_test = (string) $bundle_entity->id();
$definitions = $this->entityFieldManager->getFieldDefinitions($entity_type_id, $bundle_to_test);
if (array_key_exists($field_name, $definitions)) {
$bundle = $bundle_to_test;
break;
}
}
}
}
return $bundle;
}
/**
* Set the context of field and entity (override the method trait).
*
......@@ -202,40 +102,8 @@ abstract class ComponentFormatterBase extends FormatterBase {
* Source contexts.
*/
protected function getComponentSourceContexts(?FieldItemListInterface $items = NULL): array {
$contexts = array_merge($this->context ?? [], $this->getThirdPartySetting('ui_patterns', 'context') ?? []);
$field_definition = $this->fieldDefinition;
$field_name = $field_definition->getName() ?? "";
$contexts['field_name'] = new Context(ContextDefinition::create('string'), $field_name);
$contexts = RequirementsContext::addToContext(["field_formatter"], $contexts);
$bundle = $field_definition->getTargetBundle();
$contexts['bundle'] = new Context(ContextDefinition::create('string'), $bundle ?? "");
// Get the entity.
$entity_type_id = $field_definition->getTargetEntityTypeId();
// When field items are available, we can get the entity directly.
$entity = ($items) ? $items->getEntity() : NULL;
if (!$entity) {
$entity = $this->chainContextEntityResolver->guessEntity($contexts);
}
if (!$entity_type_id) {
return $contexts;
}
if (!$entity || !$this->checkEntityHasField($entity, $entity_type_id, $field_name)) {
// Generate a default bundle when it is missing,
// this covers contexts like the display of a field in a view.
// the bundle selected should have the field in definition...
$entity = !empty($bundle) ? $this->sampleEntityGenerator->get($entity_type_id, $bundle) :
$this->sampleEntityGenerator->get($entity_type_id, $this->findEntityBundleWithField($entity_type_id, $field_name));
}
$contexts['entity'] = EntityContext::fromEntity($entity);
return $contexts;
}
/**
* {@inheritdoc}
*/
public static function isApplicable(FieldDefinitionInterface $field_definition) {
return ($field_definition->getTargetEntityTypeId() !== NULL) && parent::isApplicable($field_definition);
$contexts = parent::getComponentSourceContexts($items);
return RequirementsContext::addToContext(["field_formatter"], $contexts);
}
/**
......
<?php
namespace Drupal\ui_patterns_field_formatters\Plugin\Field\FieldFormatter;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase as FieldFormatterBase;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\Core\Plugin\Context\EntityContext;
use Drupal\ui_patterns\SourcePluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Base class for UI Patterns field formatters.
*/
abstract class FormatterBase extends FieldFormatterBase {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The entity field manager.
*
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
*/
protected $entityFieldManager;
/**
* The sample entity generator.
*
* @var \Drupal\ui_patterns\Entity\SampleEntityGenerator
*/
protected $sampleEntityGenerator;
/**
* The chain context entity resolver.
*
* @var \Drupal\ui_patterns\Resolver\ContextEntityResolverInterface
*/
protected $chainContextEntityResolver;
/**
* The provided plugin contexts.
*
* @var array|null
*/
protected $context = NULL;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
$instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
$instance->entityTypeManager = $container->get('entity_type.manager');
$instance->entityFieldManager = $container->get('entity_field.manager');
$instance->sampleEntityGenerator = $container->get('ui_patterns.sample_entity_generator');
$instance->chainContextEntityResolver = $container->get('ui_patterns.chain_context_entity_resolver');
return $instance;
}
/**
* Set the context.
*
* @param array $context
* Context.
*
* @return void
* Nothing.
*/
public function setContext(array $context): void {
$this->context = $context;
}
/**
* {@inheritdoc}
*/
protected function checkEntityHasField(EntityInterface $entity, string $entity_type_id, string $field_name) : bool {
$field_definitions = $this->entityFieldManager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle());
return ($entity->getEntityTypeId() === $entity_type_id &&
array_key_exists($field_name, $field_definitions));
}
/**
* Find an entity bundle which has a field.
*
* @param string $entity_type_id
* The entity type id.
* @param string $field_name
* The field name to be found in searched bundle.
*
* @return string
* The bundle.
*/
protected function findEntityBundleWithField(string $entity_type_id, string $field_name) : string {
// @todo better implementation with service 'entity_type.bundle.info'
$bundle = $entity_type_id;
$bundle_entity_type = $this->entityTypeManager->getDefinition($entity_type_id)->getBundleEntityType();
if (NULL !== $bundle_entity_type) {
$bundle_list = $this->entityTypeManager->getStorage($bundle_entity_type)->loadMultiple();
if (count($bundle_list) > 0) {
foreach ($bundle_list as $bundle_entity) {
$bundle_to_test = (string) $bundle_entity->id();
$definitions = $this->entityFieldManager->getFieldDefinitions($entity_type_id, $bundle_to_test);
if (array_key_exists($field_name, $definitions)) {
$bundle = $bundle_to_test;
break;
}
}
}
}
return $bundle;
}
/**
* Set the context of field and entity (override the method trait).
*
* @param ?FieldItemListInterface $items
* Field items when available.
*
* @return array
* Source contexts.
*/
protected function getComponentSourceContexts(?FieldItemListInterface $items = NULL): array {
$contexts = array_merge($this->context ?? [], $this->getThirdPartySetting('ui_patterns', 'context') ?? []);
$field_definition = $this->fieldDefinition;
$field_name = $field_definition->getName() ?? "";
$contexts['field_name'] = new Context(ContextDefinition::create('string'), $field_name);
$bundle = $field_definition->getTargetBundle();
$contexts['bundle'] = new Context(ContextDefinition::create('string'), $bundle ?? "");
// Get the entity.
$entity_type_id = $field_definition->getTargetEntityTypeId();
// When field items are available, we can get the entity directly.
$entity = ($items) ? $items->getEntity() : NULL;
if (!$entity) {
$entity = $this->chainContextEntityResolver->guessEntity($contexts);
}
if (!$entity_type_id) {
return $contexts;
}
if (!$entity || !$this->checkEntityHasField($entity, $entity_type_id, $field_name)) {
// Generate a default bundle when it is missing,
// this covers contexts like the display of a field in a view.
// the bundle selected should have the field in definition...
$entity = !empty($bundle) ? $this->sampleEntityGenerator->get($entity_type_id, $bundle) :
$this->sampleEntityGenerator->get($entity_type_id, $this->findEntityBundleWithField($entity_type_id, $field_name));
}
$contexts['entity'] = EntityContext::fromEntity($entity);
return $contexts;
}
/**
* {@inheritdoc}
*/
public static function isApplicable(FieldDefinitionInterface $field_definition) {
return ($field_definition->getTargetEntityTypeId() !== NULL) && parent::isApplicable($field_definition);
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
$dependencies = parent::calculateDependencies();
SourcePluginBase::mergeConfigDependencies($dependencies, ["module" => ["ui_patterns_field_formatters"]]);
return $dependencies;
}
}
<?php
namespace Drupal\ui_patterns_field_formatters\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\Attribute\FieldFormatter;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Formatter to render the file URI to its download path.
*/
#[FieldFormatter(
id: 'ui_patterns_source',
label: new TranslatableMarkup('Render source (UI Patterns)'),
field_types: ['ui_patterns_source'],
)]
class SourceFormatter extends FormatterBase {
/**
* The component element builder.
*
* @var \Drupal\ui_patterns\Element\ComponentElementBuilder
*/
protected $componentElementBuilder;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
$instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
$instance->componentElementBuilder = $container->get('ui_patterns.component_element_builder');
return $instance;
}
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
$settings = parent::defaultSettings();
return $settings;
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$fake_build = [];
$contexts = $this->getComponentSourceContexts($items);
$contexts['ui_patterns:lang_code'] = new Context(new ContextDefinition('any'), $langcode);
$contexts['ui_patterns:field:items'] = new Context(new ContextDefinition('any'), $items);
for ($field_item_index = 0; $field_item_index < $items->count(); $field_item_index++) {
$contexts['ui_patterns:field:index'] = new Context(new ContextDefinition('integer'), $field_item_index);
$source_with_configuration = $items->get($field_item_index)->getValue();
$fake_build = $this->componentElementBuilder->buildSource($fake_build, 'content', [], $source_with_configuration, $contexts);
}
$build = $fake_build['#slots']['content'] ?? [];
$build['#cache'] = $fake_build['#cache'] ?? [];
return $build;
}
}
ui_patterns_ui.component_display.*.*.*:
label: Component display
type: config_entity
mapping:
id:
type: string
label: ID
label:
type: label
label: Label
status:
type: integer
label: Status
uuid:
type: string
component_id:
type: string
label: Component Id
form_mode_name:
type: string
label: Form Mode Name
content:
type: sequence
label: 'Slot or prop Configuration'
sequence:
type: ui_patterns_ui.component_display
hidden:
type: sequence
label: 'Component display setting'
sequence:
type: boolean
label: 'Value'
ui_patterns_ui.component_display:
type: ui_patterns_widget_settings
mapping:
source_id:
type: string
label: 'source'
source:
label: 'source'
type: ui_patterns_source.[%parent.source_id]
parent:
type: string
label: 'Parent'
widget_settings:
type: ui_patterns_ui.widget_settings.[%parent.type]
label: 'Component Display'
weight:
type: string
label: 'Weight'
region:
type: string
label: 'Region'
third_party_settings:
type: sequence
label: 'List of third party settings'
sequence:
type: mixed
ui_patterns_widget_settings:
type: mapping
label: 'Widget settings'
mapping:
type:
type: string
label: 'Format type machine name'
label:
type: label
label: 'Label setting machine name'
widget_settings:
type: ui_patterns_ui.widget_settings.[%parent.type]
label: 'Widget Settings'
# Default schema for entity display field with undefined type.
ui_patterns_ui.widget_settings.*:
type: mapping
mapping:
title:
type: label
label: 'Title'
title_display:
type: string
label: 'Title display'
description:
type: text
label: 'Description'
description_display:
type: string
label: 'Description display'
required:
type: integer
label: 'Widget is required'
<?php
declare(strict_types=1);
namespace Drupal\ui_patterns_ui;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
/**
* Provides an interface defining a component display entity type.
*/
interface ComponentFormDisplayInterface extends ConfigEntityInterface {
/**
* Gets the highest weight of the display options in the display.
*
* @return int|null
* The highest weight of the display options in the display, or NULL if the
* display is empty.
*/
public function getHighestWeight(): int|null;
/**
* Gets the display options for all components.
*
* @return array
* The array of display options, keyed by component name.
*/
public function getPropSlotOptions(): array;
/**
* Gets the display options set for a component.
*
* @param string $name
* The name of the component.
*
* @return array|null
* The display options for the component, or NULL if the component is not
* displayed.
*/
public function getPropSlotOption(string $name);
/**
* Sets the display options for a prop or slot.
*
* @param string $name
* The name of the prop or slot.
* @param array $options
* The display options.
*
* @return $this
*/
public function setPropSlotOptions(string $name, array $options = []);
/**
* Sets a prop or slot to be hidden.
*
* @param string $name
* The name of the prop or slot.
*
* @return $this
*/
public function removePropSlotOption(string $name);
/**
* Returns the form mode name.
*
* @return ?string
* The form mode name.
*/
public function getFormModeName(): ?string;
/**
* Returns the sdc component id.
*
* @return string
* The sdc component id.
*/
public function getComponentId():string;
}
<?php
declare(strict_types=1);
namespace Drupal\ui_patterns_ui\Controller;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Plugin\Component;
use Drupal\Core\Routing\TrustedRedirectResponse;
use Drupal\Core\Theme\ComponentPluginManager;
use Drupal\Core\Url;
use Drupal\ui_patterns_ui\Entity\ComponentFormDisplay;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
* Provides a listing of component displays.
*/
final class ComponentController extends ControllerBase {
/**
* Redirects to the default route of a specified component.
*/
public function forward(string $component_id):RedirectResponse {
$component_plugin_manager = self::getComponentPluginManager();
$component = $component_plugin_manager->find($component_id);
return (new TrustedRedirectResponse($this->getDefaultRoute($component)
->toString()))
->addCacheableDependency((new CacheableMetadata())->setCacheMaxAge(0));
}
/**
* Renders a list of components in a table format.
*/
public function render(): array {
$build['table'] = [
'#type' => 'table',
'#header' => $this->buildHeader(),
'#title' => $this->t('Component List'),
'#rows' => [],
'#empty' => $this->t('There are no @label yet.', ['@label' => '']),
];
foreach ($this->load() as $component) {
if ($row = $this->buildComponentRow($component)) {
$build['table']['#rows'][$component->getPluginId()] = $row;
}
}
$build['pager'] = [
'#type' => 'pager',
];
return $build;
}
/**
* The component plugin manager.
*/
public static function getComponentPluginManager(): ComponentPluginManager {
return \Drupal::service('plugin.manager.sdc');
}
/**
* Load all components, sorted by provider and label.
*/
public function load(): array {
$component_plugin_manager = self::getComponentPluginManager();
$definitions = $component_plugin_manager->getDefinitions();
uasort($definitions, function ($a, $b) {
$comp = strnatcasecmp((string) ($a['provider'] ?? ''), (string) ($b['provider'] ?? ''));
return ($comp === 0) ? strnatcasecmp((string) ($a['label'] ?? ''), (string) ($b['label'] ?? '')) : $comp;
});
$plugin_ids = array_keys($definitions);
// @phpstan-ignore-next-line
return array_values(array_filter(array_map(
[$component_plugin_manager, 'createInstance'],
$plugin_ids
)));
}
/**
* Builds the header row for the component listing.
*
* @return array
* A render array structure of header strings.
*/
public function buildHeader(): array {
$header['label'] = $this->t('Label');
$header['provider'] = $this->t('Provider');
$header['operations'] = $this->t('Operations');
return $header;
}
/**
* Build one component row.
*/
public function buildComponentRow(Component $component): array {
$definition = $component->getPluginDefinition();
$row['label'] = $component->metadata->name;
$row['provider'] = is_array($definition) ? $definition['provider'] : '';
$row['operations']['data'] = [
'#type' => 'operations',
'#links' => $this->getComponentOperations($component),
];
return $row;
}
/**
* Retrieves the default route for a given component.
*
* This function determines the appropriate URL for the default form display
* of a component. If a default display exists, it returns its URL; otherwise,
* it constructs a route URL for adding a new form display.
*
* @param \Drupal\Core\Plugin\Component $component
* The component for which the default route is being retrieved.
*
* @return \Drupal\Core\Url
* The URL object representing the default route for the component.
*/
private function getDefaultRoute(Component $component) {
$default_display = ComponentFormDisplay::loadDefault($component->getPluginId());
if ($default_display !== NULL) {
return $default_display->toUrl();
}
else {
$route = 'entity.component_form_display.' . $component->getPluginId() . '.add_form';
return Url::fromRoute($route, [
'component_id' => $component->getPluginId(),
]);
}
}
/**
* Returns component operations.
*/
protected function getComponentOperations(Component $component): array {
$operations['configure'] = [
'title' => $this->t('Manage form display'),
'url' => $this->getDefaultRoute($component),
'attributes' => [],
'weight' => 50,
];
return $operations;
}
}
<?php
declare(strict_types=1);
namespace Drupal\ui_patterns_ui\Element;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element\FormElementBase;
use Drupal\ui_patterns\SourcePluginBase;
use Drupal\ui_patterns\SourcePluginManager;
use Drupal\ui_patterns_ui\Entity\ComponentFormDisplay;
/**
* Component display form element.
*
* @FormElement("uip_display_form")
*/
class UiPComponentFormDisplayForm extends FormElementBase {
/**
* Returns the source manager.
*/
public static function getSourceManager(): SourcePluginManager {
return \Drupal::service('plugin.manager.ui_patterns_source');
}
/**
* {@inheritdoc}
*/
public function getInfo() {
$class = get_class($this);
return [
'#input' => TRUE,
'#multiple' => FALSE,
'#default_value' => NULL,
'#source_contexts' => [],
'#tag_filter' => [],
'#process' => [
[$class, 'buildForm'],
],
'#after_build' => [
[$class, 'afterBuild'],
],
'#element_validate' => [
[$class, 'elementValidate'],
],
'#theme_wrappers' => ['form_element'],
];
}
/**
* {@inheritdoc}
*/
public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
if (is_array($input)) {
$display_id = $element['#display_id'];
$display = ComponentFormDisplay::load($display_id);
$variant_id = NULL;
$props = [];
$slots = [];
foreach ($input as $key => $source) {
if ($key === 'variant') {
$variant_id = $source;
continue;
}
if ($display->isSlot($key)) {
$slots[$key]['sources'][] = $source;
}
else {
$props[$key] = $source;
}
}
$output = [
'component_id' => $element['#component_id'],
'display_id' => $element['#display_id'],
'variant_id' => $variant_id,
'props' => $props,
'slots' => $slots,
];
$element['#default_value'] = $output;
return $output;
}
else {
return [
'component_id' => NULL,
'variant_id' => NULL,
'props' => [],
'slots' => [],
];
}
}
/**
* Alter the element during validation.
*
* @param array $element
* The element.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*/
public static function elementValidate(array &$element, FormStateInterface $form_state) : void {
$element['#value'] = self::valueCallback($element, $form_state->getValue($element['#parents']), $form_state);
if (isset($element['#value'])) {
$form_state->setValueForElement($element, $element['#value']);
}
}
/**
* {@inheritdoc}
*/
public static function afterBuild(array $element, FormStateInterface $form_state) : array {
return $element;
}
/**
* {@inheritdoc}
*/
public static function buildForm(array $element, FormStateInterface $form_state): array {
$display_id = $element['#display_id'] ?? '';
$display = $display_id ? ComponentFormDisplay::load($display_id) : NULL;
if (!$display) {
return $element;
}
$element['#display'] = $display;
$slot_prop_options = $display->getPropSlotOptions();
foreach ($slot_prop_options as $prop_slot_id => $slot_prop_option) {
if ($slot_prop_option['region'] !== 'content') {
continue;
}
$source_id = $slot_prop_option['source_id'] ?? NULL;
if ($source_id === NULL) {
continue;
}
$source_plugin_manager = self::getSourceManager();
$prop_definition = $display->getPropDefinition($prop_slot_id);
if ($prop_definition === NULL) {
// Continue if the prop definition is removed from SDC component.
continue;
}
if ($prop_slot_id === 'variant') {
$configuration = $element['#default_value']['variant_id'] ?? [];
}
else {
$configuration = $element['#default_value']['props'][$prop_slot_id] ?? $element['#default_value']['slots'][$prop_slot_id]['sources'][0] ?? [];
}
$source_contexts = [];
$form_array_parents = $element['#array_parents'];
$configuration['widget_settings'] = $slot_prop_option['widget_settings'] ?? [];
if (!isset($configuration['widget_settings']['title_display'])) {
$configuration['widget_settings']['title_display'] = 'before';
}
$source_configuration = SourcePluginBase::buildConfiguration($prop_slot_id, $prop_definition, $configuration, $source_contexts, $form_array_parents);
/** @var \Drupal\ui_patterns\SourcePluginBase $source */
$source = $source_plugin_manager->createInstance($source_id, $source_configuration);
$form = $source->settingsForm([], $form_state);
$element[$prop_slot_id]['#type'] = 'container';
$element[$prop_slot_id]['source'] = $form;
$element[$prop_slot_id]['source_id'] = ['#type' => 'hidden', '#default_value' => $source_id];
}
return $element;
}
}
<?php
declare(strict_types=1);
namespace Drupal\ui_patterns_ui\Element;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\ui_patterns\Element\ComponentForm;
/**
* Provides a Component form builder element.
*
* @FormElement("uip_displays_form")
*/
class UiPComponentFormDisplaysForm extends ComponentForm {
/**
* {@inheritdoc}
*/
public function getInfo() {
return array_merge(
['#include_display_ids' => NULL],
parent::getInfo()
);
}
/**
* Returns the entity type manager.
*/
public static function getEntityTypeManager(): EntityTypeManagerInterface {
return \Drupal::entityTypeManager();
}
/**
* Get form displays displays for a component.
*
* @param string $component_id
* The component id.
*
* @return \Drupal\ui_patterns_ui\Entity\ComponentFormDisplay[]
* The component form displays.
*/
protected static function getComponentDisplays(string $component_id) : array {
$storage = self::getEntityTypeManager()->getStorage('component_form_display');
/** @var \Drupal\ui_patterns_ui\Entity\ComponentFormDisplay[] $matched_displays */
$matched_displays = $storage->loadByProperties([
'component_id' => $component_id,
'status' => TRUE,
]);
uasort($matched_displays, function ($a, $b) {
return strnatcasecmp((string) $a->label(), (string) $b->label());
});
return $matched_displays;
}
/**
* Build the display selector.
*/
protected static function buildSelectComponentDisplays(array &$element, array $matched_displays, string $wrapper_id, ?string $selected_display_id = NULL) : void {
$options = [];
foreach ($matched_displays as $matched_display) {
if (is_array($element['#include_display_ids'])) {
if (!in_array($matched_display->id(), $element['#include_display_ids'])) {
continue;
}
}
$options[$matched_display->id()] = $matched_display->label();
}
if (\Drupal::currentUser()->hasPermission('access ui patterns component form')) {
$options['_component_form'] = t('Component form');
}
$element['display_id'] = [
'#type' => 'select',
'#options' => $options,
'#attributes' => ['class' => ['uip-display-select']],
'#default_value' => $selected_display_id ?? array_keys($options)[0],
'#ajax' => [
'callback' => [static::class, 'changeSelectorFormChangeAjax'],
'wrapper' => $wrapper_id,
'effect' => 'fade',
],
'#executes_submit_callback' => FALSE,
'#access' => count($options) > 1,
];
}
/**
* Build the display form.
*/
protected static function buildComponentFormDisplay(array &$element, FormStateInterface $form_state, string $component_id, array $matched_displays) : void {
$all_form_state_values = $form_state->getValues();
$form_state_values = &NestedArray::getValue($all_form_state_values, $element['#parents'] ?? []);
$all_input_values = $form_state->getUserInput();
$input_values = &NestedArray::getValue($all_input_values, $element['#parents'] ?? []);
$display_id = $element['#default_value']['display_id'] ?? NULL;
// If there is already an existing config use _component_form
// to display this config.
if ($display_id === NULL && (
count($element['#default_value']['props'] ?? []) !== 0 ||
count($element['#default_value']['slots'] ?? []) !== 0)) {
$display_id = '_component_form';
}
if (isset($form_state_values['display_id'])) {
$display_id = $form_state_values['display_id'];
}
elseif (isset($input_values['display_id'])) {
$display_id = $input_values['display_id'];
}
$wrapper_id = static::getElementId($element, 'display');
static::buildSelectComponentDisplays($element, $matched_displays, $wrapper_id, $display_id);
$display_id = $element['display_id']['#default_value'];
$element["display"] = [
'#type' => 'container',
'#attributes' => ['id' => $wrapper_id],
'component_id' => [
'#type' => 'hidden',
'#value' => $component_id,
],
'display_id' => [
'#type' => 'hidden',
'#value' => $display_id,
],
];
if ($display_id === '_component_form') {
$element["display"]['value_' . Html::getId($display_id)] = [
'#type' => 'component_form',
'#allow_override' => TRUE,
'#component_id' => $component_id,
'#component_required' => $element['#component_required'] ?? FALSE,
'#component_validation' => FALSE,
'#tag_filter' => $element['#tag_filter'] ?? [],
'#ajax_url' => $element['#ajax_url'] ?? NULL,
'#source_contexts' => $element['#source_contexts'] ?? [],
'#render_slots' => $element['#render_slots'] ?? TRUE,
'#render_props' => $element['#render_props'] ?? TRUE,
'#default_value' => $element['#default_value'],
'#wrap' => $element['#wrap'] ?? TRUE,
'#prop_filter' => $element['#prop_filter'] ?? NULL,
'#render_headings' => $element['#render_headings'] ?? TRUE,
'#render_sources' => $element['#render_sources'] ?? TRUE,
];
}
elseif ($display_id) {
$element["display"]['value_' . Html::getId($display_id)] = [
'#type' => 'uip_display_form',
'#display_id' => $display_id,
'#component_id' => $component_id,
'#default_value' => $element['#default_value'],
];
}
}
/**
* {@inheritdoc}
*/
public static function buildForm(array &$element, FormStateInterface $form_state) : array {
$element = ComponentForm::buildForm($element, $form_state);
// Retrieve component id.
$component_id = $element['#component_id'] ?? $element['#default_value']['component_id'] ?? NULL;
$matched_displays = ($component_id !== NULL) ? static::getComponentDisplays($component_id) : [];
if (count($matched_displays) === 0) {
return $element;
}
// Hide elements from component form.
$element["variant_id"]['#access'] = FALSE;
$element["props"]['#access'] = FALSE;
$element["slots"]['#access'] = FALSE;
$element['#component_validation'] = TRUE;
$element["#after_build"] = [
[static::class, 'afterBuild'],
];
// Display ID.
static::buildComponentFormDisplay($element, $form_state, $component_id, $matched_displays);
return $element;
}
/**
* Ajax callback for display selector change.
*/
public static function changeSelectorFormChangeAjax(
array $form,
FormStateInterface $form_state,
) : array {
$parents = $form_state->getTriggeringElement()['#array_parents'];
$parents = array_merge(array_slice($parents, 0, -1), ['display']);
$sub_form = &NestedArray::getValue($form, $parents);
return $sub_form ?? [];
}
/**
* {@inheritdoc}
*/
public static function afterBuild(array $element, FormStateInterface $form_state) : array {
return $element;
}
/**
* {@inheritdoc}
*/
public static function elementValidate(array &$element, FormStateInterface $form_state): void {
$display_id = $element['display']['display_id']['#value'] ?? NULL;
if (empty($display_id) || !isset($element['display']['value_' . Html::getId($display_id)]['#value'])) {
return;
}
$value = $element['display']['value_' . Html::getId($display_id)]['#value'];
$element['#value'] = $value;
if (($element['display']['display_id']['#value'] ?? '') !== '_component_form') {
$element['#value']['display_id'] = $element['display']['display_id']['#value'];
}
$element['#value']['component_id'] = $element['display']['component_id']['#value'];
$form_state->setValueForElement($element, $element['#value']);
}
}
<?php
declare(strict_types=1);
namespace Drupal\ui_patterns_ui\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Theme\ComponentPluginManager;
use Drupal\Core\Url;
use Drupal\ui_patterns\Plugin\UiPatterns\PropType\SlotPropType;
use Drupal\ui_patterns\PropTypePluginBase;
use Drupal\ui_patterns\SourcePluginBase;
use Drupal\ui_patterns\SourcePluginManager;
use Drupal\ui_patterns_ui\ComponentFormDisplayInterface;
/**
* Defines the component display entity type.
*
* @ConfigEntityType(
* id = "component_form_display",
* label = @Translation("Component form display"),
* label_collection = @Translation("Component form displays"),
* label_singular = @Translation("Component form display"),
* label_plural = @Translation("Component form displays"),
* label_count = @PluralTranslation(
* singular = "@count component display",
* plural = "@count component displays",
* ),
* config_prefix = "component_display",
* admin_permission = "administer component_display",
* entity_keys = {
* "id" = "id",
* "label" = "label",
* "uuid" = "uuid",
* },
* handlers = {
* "form" = {
* "add" = "Drupal\ui_patterns_ui\Form\ComponentFormDisplayForm",
* "edit" = "Drupal\ui_patterns_ui\Form\ComponentFormDisplayForm",
* "delete" = "Drupal\ui_patterns_ui\Form\ComponentFormDisplayDeleteForm",
* },
* },
* links = {
* "collection" = "/admin/structure/component/{component_type}",
* "delete-form" = "/admin/structure/component/{component_type}/form-display/{form_mode_name}/delete",
* },
*
* config_export = {
* "id",
* "label",
* "component_id",
* "form_mode_name",
* "content",
* "hidden",
* },
* )
*/
final class ComponentFormDisplay extends ConfigEntityBase implements ComponentFormDisplayInterface {
/**
* The display ID.
*/
protected string $id;
/**
* The display label.
*/
protected string $label;
/**
* The sdc component id.
*/
protected string $component_id;
/**
* The form mode name.
*/
protected ?string $form_mode_name = NULL;
/**
* The plugin objects used for this display, keyed by prop name.
*
* @var array
*/
protected $plugins = [];
/**
* List of slot or props display options, keyed by name.
*
* @var array
*/
protected $content = [];
/**
* List of slots or props that are set to be hidden.
*
* @var array
*/
protected $hidden = [];
/**
* {@inheritdoc}
*/
public function id() {
return str_replace(':', '.', $this->getComponentId()) . '.' . $this->getFormModeName();
}
/**
* Returns the component plugin manager.
*/
public static function getComponentPluginManager(): ComponentPluginManager {
return \Drupal::service('plugin.manager.sdc');
}
/**
* Returns the source plugin manager.
*/
public static function getSourcePluginManager(): SourcePluginManager {
return \Drupal::service('plugin.manager.ui_patterns_source');
}
/**
* {@inheritdoc}
*/
public function getComponentId():string {
return $this->component_id;
}
/**
* Checks if the prop_id is a slot or prop.
*/
public function isSlot(string $prop_id): bool {
return $this->getPropType($prop_id) instanceof SlotPropType;
}
/**
* Returns the prop definition.
*/
public function getPropDefinition(string $prop_id): array | NULL {
$component_definition = self::getComponentPluginManager()->getDefinition($this->component_id);
if (isset($component_definition['props']['properties'][$prop_id])) {
return $component_definition['props']['properties'][$prop_id];
}
if (isset($component_definition['slots'][$prop_id])) {
return $component_definition['slots'][$prop_id];
}
return NULL;
}
/**
* Returns the prop type.
*/
protected function getPropType(string $prop_id): ?PropTypePluginBase {
$prop_definition = $this->getPropDefinition($prop_id);
if (isset($prop_definition['ui_patterns']['type_definition'])) {
return $prop_definition['ui_patterns']['type_definition'];
}
return NULL;
}
/**
* Returns the source configuration.
*/
protected function getSourceConfiguration(string $prop_id): array {
$prop_definition = $this->getPropDefinition($prop_id);
$display_options = $this->getPropSlotOption($prop_id);
$settings = [];
$settings['widget_settings'] = $display_options['widget_settings'] ?? [];
if (!isset($settings['widget_settings']['title_display'])) {
$settings['widget_settings']['title_display'] = 'before';
}
if (!isset($settings['widget_settings']['description_display'])) {
$settings['widget_settings']['description_display'] = 'after';
}
$configuration = SourcePluginBase::buildConfiguration($prop_id, $prop_definition, $settings, [], []);
$configuration['settings'] = $display_options['source'] ?? [];
return $configuration;
}
/**
* Sets the form mode name.
*/
public function setFormModeName(string $form_mode_name):void {
$this->form_mode_name = $form_mode_name;
}
/**
* {@inheritdoc}
*/
public function getFormModeName(): ?string {
return $this->form_mode_name;
}
/**
* {@inheritdoc}
*/
protected function urlRouteParameters($rel) {
$params = parent::urlRouteParameters($rel);
$params['component_id'] = $this->getComponentId();
$params['form_mode_name'] = $this->getFormModeName();
return $params;
}
/**
* Returns source plugins.
*/
public function getSourcePlugins(string $prop_id): array {
$component = $this->getPropSlotOption($prop_id);
$sources = self::getSourcePluginManager()->getDefinitionsForPropType($this->getPropType($prop_id)->getPluginId());
return self::getSourcePluginManager()->createInstances(array_keys($sources), ['widget_settings' => $component['widget_settings'] ?? []]);
}
/**
* Returns the default source plugin.
*/
public function getDefaultSourcePlugin(string $prop_id): ?SourcePluginBase {
$source_id = self::getSourcePluginManager()->getPropTypeDefault($this->getPropType($prop_id)->getPluginId());
return $source_id ? $this->getSourcePlugin($prop_id, $source_id) : NULL;
}
/**
* Returns a source plugin for prop and source id.
*/
public function getSourcePlugin(string $prop_id, string $source_id): ?SourcePluginBase {
/** @var \Drupal\ui_patterns\SourcePluginBase $source */
$source = self::getSourcePluginManager()->createInstance($source_id, $this->getSourceConfiguration($prop_id));
return $source;
}
/**
* Returns the selected source plugin for prop id.
*/
public function getSelectedSourcePlugin(string $prop_id): ?SourcePluginBase {
$display_options = $this->getPropSlotOption($prop_id);
$selected_source_id = !empty($display_options['source_id']) ? $display_options['source_id'] : NULL;
$plugin = NULL;
if ($selected_source_id === NULL) {
$plugin = $this->getDefaultSourcePlugin($prop_id);
}
elseif ($selected_source_id !== NULL) {
$plugin = $this->getSourcePlugin($prop_id, $selected_source_id);
}
return $plugin;
}
/**
* {@inheritdoc}
*/
public function getPropSlotOptions(): array {
$content = $this->content;
uasort($content, function ($a, $b) {
return $a['weight'] <=> $b['weight'];
});
return $content;
}
/**
* {@inheritdoc}
*/
public function getPropSlotOption($name) {
return $this->content[$name] ?? NULL;
}
/**
* {@inheritdoc}
*/
public function getHighestWeight(): ?int {
$weights = [];
// Collect weights for the components in the display.
foreach ($this->content as $options) {
if (isset($options['weight'])) {
$weights[] = $options['weight'];
}
}
return $weights ? max($weights) : NULL;
}
/**
* {@inheritdoc}
*/
public function setPropSlotOptions($name, array $options = []) {
// If no weight specified, make sure the field sinks at the bottom.
if (!isset($options['weight'])) {
$max = $this->getHighestWeight();
$options['weight'] = isset($max) ? $max + 1 : 0;
}
// Ensure we always have an empty settings and array.
$options += ['widget_settings' => [], 'third_party_settings' => []];
$this->content[$name] = $options;
unset($this->hidden[$name]);
unset($this->plugins[$name]);
return $this;
}
/**
* {@inheritdoc}
*/
public function removePropSlotOption($name) {
$this->hidden[$name] = TRUE;
unset($this->content[$name]);
unset($this->plugins[$name]);
return $this;
}
/**
* {@inheritdoc}
*/
public function toUrl($rel = NULL, array $options = []) {
// Unless language was already provided, avoid setting an explicit language.
$options += ['language' => NULL];
if ($rel === 'edit-form' || $rel === NULL) {
return Url::fromRoute('entity.component_form_display.' . $this->getComponentId() . '.edit_form', ['form_mode_name' => $this->getFormModeName()]);
}
if ($rel === 'collection') {
return Url::fromRoute('entity.component_form_display.' . $this->getComponentId());
}
return parent::toUrl($rel, $options);
}
/**
* Load form display by form mode.
*/
public static function loadByFormMode(string $component_id, mixed $form_mode): ?ComponentFormDisplayInterface {
if (is_array($form_mode)) {
// Strange behavior for default value form modes.
// @todo Debug it.
return NULL;
}
/** @var \Drupal\ui_patterns_ui\Entity\ComponentFormDisplay[] $items */
$items = \Drupal::entityTypeManager()->getStorage('component_form_display')
->loadByProperties(['component_id' => $component_id, 'form_mode_name' => $form_mode]);
return count($items) !== 0 ? current($items) : NULL;
}
/**
* Load the default display.
*/
public static function loadDefault(string $component_id): ?ComponentFormDisplayInterface {
/** @var \Drupal\ui_patterns_ui\Entity\ComponentFormDisplay[] $items */
$items = \Drupal::entityTypeManager()->getStorage('component_form_display')
->loadByProperties(['component_id' => $component_id]);
return count($items) !== 0 ? current($items) : NULL;
}
}
<?php
declare(strict_types=1);
namespace Drupal\ui_patterns_ui\Form;
use Drupal\Core\Entity\EntityDeleteForm;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\ui_patterns_ui\Entity\ComponentFormDisplay;
/**
* Component display form.
*/
final class ComponentFormDisplayDeleteForm extends EntityDeleteForm {
/**
* {@inheritdoc}
*/
public function getEntityFromRouteMatch(RouteMatchInterface $route_match, $entity_type_id) {
$component_id = $route_match->getParameter('component_id');
$form_mode = $route_match->getParameter('form_mode_name');
return ComponentFormDisplay::loadByFormMode($component_id, $form_mode);
}
}
This diff is collapsed.
<?php
namespace Drupal\ui_patterns_ui\Plugin\Derivative;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Theme\ComponentPluginManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides local action definitions for all component forms.
*/
class UiPatternsUiLocalAction extends DeriverBase implements ContainerDeriverInterface {
use StringTranslationTrait;
/**
* Constructs a UiPatternsUiLocalAction object.
*/
public function __construct(protected RouteProviderInterface $routeProvider, protected ComponentPluginManager $componentPluginManager, protected EntityTypeManagerInterface $entityTypeManager) {
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$container->get('router.route_provider'),
$container->get('plugin.manager.sdc'),
$container->get('entity_type.manager'),
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
$this->derivatives = [];
$components = $this->componentPluginManager->getAllComponents();
foreach ($components as $component) {
$this->derivatives["component_form_display.{$component->getPluginId()}"] = [
'route_name' => "entity.component_form_display.{$component->getPluginId()}.add_form",
'title' => $this->t('Add form display'),
'appears_on' => ["entity.component_form_display.{$component->getPluginId()}.edit_form"],
];
}
foreach ($this->derivatives as &$entry) {
$entry += $base_plugin_definition;
}
return $this->derivatives;
}
}