diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..e5384a8cf9d2743a1ddee2df1f7ed5d3631399a1 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,29 @@ +################ +# GitLabCI template for Drupal projects. +# +# This template is designed to give any Contrib maintainer everything they need to test, without requiring modification. +# It is also designed to keep up to date with Core Development automatically through the use of include files that can be centrally maintained. +# As long as you include the project, ref and three files below, any future updates added by the Drupal Association will be used in your +# pipelines automatically. However, you can modify this template if you have additional needs for your project. +# The full documentation is on https://project.pages.drupalcode.org/gitlab_templates/ +################ + +# For information on alternative values for 'ref' see https://project.pages.drupalcode.org/gitlab_templates/info/templates-version/ +# To test a Drupal 7 project, change the first include filename from .main.yml to .main-d7.yml +include: + - project: $_GITLAB_TEMPLATES_REPO + ref: $_GITLAB_TEMPLATES_REF + file: + - "/includes/include.drupalci.main.yml" + - "/includes/include.drupalci.variables.yml" + - "/includes/include.drupalci.workflows.yml" + +################ +# Pipeline configuration variables are defined with default values and descriptions in the file +# https://git.drupalcode.org/project/gitlab_templates/-/blob/main/includes/include.drupalci.variables.yml +# Uncomment the lines below if you want to override any of the variables. The following is just an example. +################ +# variables: +# SKIP_ESLINT: '1' +# OPT_IN_TEST_NEXT_MAJOR: '1' +# _CURL_TEMPLATES_REF: 'main' diff --git a/README.md b/README.md index 33bfee546c49ede138ccf0192ab9e581effac9c8..f63668eda5daa65d45dc9cc6d8dacced9cd5e05b 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,33 @@ # Component Fields -The Component Fields module provides a mechanism for automatically calculating the value of some ("final") field based on the values of 2 other ("component") fields of the same type. The final value could be as simple as a copy of one of the component values or a concatenation/merging of the 2 component values, or anything else as complex as you can code in a PHP function. This can be done uniformly on some field for all entities of a given bundle, or overriden per-entity. - -The base idea is to make each component field accessible by different groups of users (or procedures, like imports), store each component value in its own field, but present a single field as the final, usable value. +The Component Fields module provides a mechanism for automatically calculating +the value of some ("final") field based on the values of 2 other ("component") +fields of the same type. The final value could be as simple as a copy of one of +the component values or a concatenation/merging of the 2 component values, or +anything else as complex as you can code in a PHP function. This can be done +uniformly on some field for all entities of a given bundle, +or overriden per-entity. + +The base idea is to make each component field accessible by different groups of +users (or procedures, like imports), store each component +value in its own field, +but present a single field as the final, usable value. ## Examples -- A field_text is configured to have field_text_A and field_text_B as components, and its value should be the concatenation of the values of field_text_A and field_text_B. -- A field_tags should be the union of the values of field_tags_A and field_tags_B. -- Some content type is being regularly imported from an external source. The content moderation team must be able to override the imported values for some fields, without them being reset on the next import. The imported values can be stored in field_imported, and the overridden values in field_override. The field_final can be set to use field_override if it is not empty, otherwise to use field_imported. The end system will use the field_final, not caring about the 2 component fields. +- A field_text is configured to have field_text_A and +field_text_B as components, +and its value should be the concatenation of the values of field_text_A and f +ield_text_B. +- A field_tags should be the union of the values of +field_tags_A and field_tags_B. +- Some content type is being regularly imported from an external source. The +content moderation team must be able to override the imported values for some +fields, without them being reset on the next import. The imported values can +be stored in field_imported, and the overridden values in field_override. The +field_final can be set to use field_override if it is not empty, otherwise +to use field_imported. The end system will use the field_final, not caring +about the 2 component fields. ## What does the module provide? @@ -16,41 +35,72 @@ A plugin type `ComponentFieldsCompiler`, with a few simple implementations: - Component 1: The final value is the value of the first component. - Component 2: The final value is the value of the second component. -- Component 1 with fallback 2: The final value is the value of the first component, if non empty, or else the second. -- Component 2 with fallback 1: The final value is the value of the second component, if non empty, or else the first. -- Merge: (Can only be used by multivalue fields) The final value is the union of the values of the 2 components. +- Component 1 with fallback 2: The final value is the value of the +first component, if non empty, or else the second. +- Component 2 with fallback 1: The final value is the value of the +second component, if non empty, or else the first. +- Merge: (Can only be used by multivalue fields) The final value is +the union of the values of the 2 components. - Empty value: The final value is set explicitly empty. -A configuration page where you can define field combinations (final + 2 components) and their default compiler. In another configuration page, you can enable the option for per-entity overrides for any bundle that has compilable fields. +A configuration page where you can define field combinations +(final + 2 components) +and their default compiler. In another configuration page, +you can enable the option +for per-entity overrides for any bundle that has compilable fields. -A widget for the override field, that presents the list of compilers as radio buttons for each compilable field. +A widget for the override field, that presents the list of compilers +as radio buttons for each compilable field. ## What does the module not provide? -The module does not provide any visibility or access control, either for the final or for the component fields. This is left to the field settings and permissions which must be set up separately, outside the scope of this module. It is expected that the final fields will not be writable by anyone, since their values are calculated on a pre_save hook. +The module does not provide any visibility or access control, either for the +final or for the component fields. This is left to the field settings and +permissions which must be set up separately, outside the scope of this module. +It is expected that the final fields will not be writable by anyone, since +their values are calculated on a pre_save hook. ## Configuration - Enable the module. -- On `admin/config/component-fields/settings` you can see a list of all content entity types with their bundles. Enable the ones of your interest and save the form. -- On `admin/config/component-fields/settings/fields` you can see a fieldset for each one of the enabled bundles. Start adding groups of files: - - To be able to add a group, the bundle must have at least 3 fields of the same type that have not already been used in other groups. +- On `admin/config/component-fields/settings` you can see a list of all +content entity types with their bundles. Enable the ones of your +interest and save the form. +- On `admin/config/component-fields/settings/fields` you can see a +fieldset for each one of the enabled bundles. Start adding groups of files: + - To be able to add a group, the bundle must have at least 3 fields of + the same type that have not already been used in other groups. - First select the final field for this group from the dropdown. - Then 2 other dropdowns will appear, one for each of the component fields. - - After selecting the 2 component fields, a 4th dropdown will appear, with the available compilers. Choose the default compiler for this field of this bundle. -- On `admin/config/component-fields/settings/overrides` you can enable the option for per-entity overrides for any bundle that was previously configured. - - The overrides are stored in string_long fields as stringified json objects. This means that, to enable the overrides for some bundle, there needs to be at least one string_long field in the bundle that hasn't been configured as part of any composable field group. + - After selecting the 2 component fields, a 4th dropdown will appear, + with the available compilers. Choose the default compiler + for this field of this bundle. +- On `admin/config/component-fields/settings/overrides` you can enable +the option for per-entity overrides for any bundle that +was previously configured. + - The overrides are stored in string_long fields as stringified json + objects. This means that, to enable the overrides for some bundle, + there needs to be at least one string_long field in the bundle that + hasn't been configured as part of any composable field group. ## Things to consider -- The compilation of the final value happens on the entity save. This means that if you change the default compiler for some field, the final value will not be recalculated for the existing entities. You must resave each entity, manually either through GUI, drush command, or any other way in bulk. +- The compilation of the final value happens on the entity save. +This means that if you change the default compiler for some field, +the final value will not be recalculated for the existing entities. +You must resave each entity, manually either through GUI, drush +command, or any other way in bulk. ## TODO - Write tests! -- When the default compiler is changed, add some control and notify the user if there are entities that will be affected by the change. +- When the default compiler is changed, add some control and +notify the user if there are entities that will be affected by the change. - Add a way to recalculate the final value for all entities of a bundle. -- Add a way to recalculate only the override values, if exist, when a default compiler is changed. -- Improve GUI: Merge the enabled-bundles and the fields configuration pages into a single one? -- Maybe extend the logic to support compilable fields of different types? +- Add a way to recalculate only the override values, if exist, +when a default compiler is changed. +- Improve GUI: Merge the enabled-bundles and the fields +configuration pages into a single one? +- Maybe extend the logic to support compilable fields +of different types? - Maybe extend for more than 2 component fields? diff --git a/component_fields.module b/component_fields.module index 52a681040adcc78ae0c637d2b3048c1e94cc19fe..e74af302e63d0d70c7ecaf7d17e3498ef758df5d 100644 --- a/component_fields.module +++ b/component_fields.module @@ -1,8 +1,13 @@ <?php +/** + * @file + * Provides a form for importing taxonomies in the taxonomy_fast_import module. + */ + use Drupal\component_fields\Form\ComponentFieldsBaseConfigForm; -use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\ContentEntityInterface; +use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Form\FormStateInterface; /** @@ -13,13 +18,12 @@ function component_fields_entity_presave(EntityInterface $entity) { return; } - // @TODO check if enabled, and if it has actual fields configured! - + // @todo check if enabled, and if it has actual fields configured! // Handle fields compilation. $compile_service = Drupal::getContainer() ->get('component_fields.compile_service'); $compile_service->compileFields($entity); - // @TODO check about module priority (weight) and if it should be somehow configurable. + // @todo check about module priority (weight) and if it should be somehow configurable. } /** diff --git a/src/Annotation/ComponentFieldsCompiler.php b/src/Annotation/ComponentFieldsCompiler.php index 3f92f6d149321f69036880b698926170826e11b3..9b63595e112474f44cfa6208646f99d1ccbbd414 100644 --- a/src/Annotation/ComponentFieldsCompiler.php +++ b/src/Annotation/ComponentFieldsCompiler.php @@ -5,20 +5,40 @@ namespace Drupal\component_fields\Annotation; use Drupal\Component\Annotation\Plugin; /** + * Defines a Component Fields Compiler plugin annotation. + * + * This annotation is used to define plugins that calculate final field values + * based on component fields. + * * @Annotation */ final class ComponentFieldsCompiler extends Plugin { + /** + * The unique identifier for the plugin. + * + * @var string + */ public readonly string $id; + /** + * The title of the plugin. + * + * @var string + */ public readonly string $title; + /** + * A description of the plugin's functionality. + * + * @var string + */ public readonly string $description; /** - * Whether this plugin can be used on multivalue fields only or not. + * Indicates whether this plugin can be used on multivalue fields only. * - * @var bool $multivalue_only + * @var bool */ public readonly bool $multivalue_only; diff --git a/src/CompileService.php b/src/CompileService.php index 60e748f0bf866fdbd3c5adc406792342ac84d22b..26c9ae5c39cd0c242bea6465d91ef12422fda1b0 100644 --- a/src/CompileService.php +++ b/src/CompileService.php @@ -3,28 +3,75 @@ namespace Drupal\component_fields; use Drupal\Component\Plugin\PluginManagerInterface; +use Drupal\component_fields\DTO\ComposableField; use Drupal\component_fields\Form\ComponentFieldsBaseConfigForm; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityFieldManagerInterface; use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; -use Drupal\Core\Entity\ContentEntityInterface; -use Drupal\component_fields\DTO\ComposableField; +/** + * Service for compiling fields based on composable configurations. + */ class CompileService implements CompileServiceInterface { + /** + * The entity field manager service. + * + * @var \Drupal\Core\Entity\EntityFieldManagerInterface + */ private EntityFieldManagerInterface $entityFieldManager; + /** + * The entity type manager service. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ private EntityTypeManagerInterface $entityTypeManager; + /** + * The configuration factory service. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ private ConfigFactoryInterface $configFactory; + /** + * The entity type bundle info service. + * + * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface + */ private EntityTypeBundleInfoInterface $entityTypeBundleInfo; + /** + * The plugin manager for component fields compilers. + * + * @var \Drupal\Component\Plugin\PluginManagerInterface + */ private PluginManagerInterface $manager; + /** + * Cached array of all composable fields. + * + * @var array + */ private array $allComposableFields = []; + /** + * Constructs a CompileService instance. + * + * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager + * The entity field manager service. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager service. + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The configuration factory service. + * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info + * The entity type bundle info service. + * @param \Drupal\Component\Plugin\PluginManagerInterface $component_fields_compiler_plugin_manager + * The plugin manager for component fields compilers. + */ public function __construct(EntityFieldManagerInterface $entity_field_manager, EntityTypeManagerInterface $entity_type_manager, ConfigFactoryInterface $config_factory, EntityTypeBundleInfoInterface $entity_type_bundle_info, PluginManagerInterface $component_fields_compiler_plugin_manager) { $this->entityFieldManager = $entity_field_manager; $this->entityTypeManager = $entity_type_manager; @@ -33,6 +80,17 @@ class CompileService implements CompileServiceInterface { $this->manager = $component_fields_compiler_plugin_manager; } + /** + * Returns all composable fields grouped by field type. + * + * @param string $entity_type_id + * The entity type ID. + * @param string $bundle_id + * The bundle ID. + * + * @return array + * An array of grouped composable fields. + */ public function getAllComposableFieldsGroupped(string $entity_type_id, string $bundle_id): array { $composable_fields = $this->getAllComposableFields(); $groupped_fields = []; @@ -43,6 +101,12 @@ class CompileService implements CompileServiceInterface { return $groupped_fields; } + /** + * Returns all composable fields configured in the system. + * + * @return array + * An array of all composable fields. + */ public function getAllComposableFields(): array { if (!empty($this->allComposableFields)) { return $this->allComposableFields; @@ -87,16 +151,12 @@ class CompileService implements CompileServiceInterface { } // The $temp_count array must have at least one element with value > 2. // If not, remove the bundle from the array. - if (count(array_filter($temp_count, function($value) { - return $value > 2; - })) === 0) { + if (count(array_filter($temp_count, fn($value) => $value > 2)) === 0) { unset($this->allComposableFields[$entity_type_id]['bundles'][$bundle_id]); } if (isset($this->allComposableFields[$entity_type_id]['bundles'][$bundle_id])) { // Remove from the "fields", all the fields that are not repeated. - $this->allComposableFields[$entity_type_id]['bundles'][$bundle_id]['fields'] = array_filter($this->allComposableFields[$entity_type_id]['bundles'][$bundle_id]['fields'], function($field) use ($temp_count) { - return $temp_count[$field['type']] > 2; - }); + $this->allComposableFields[$entity_type_id]['bundles'][$bundle_id]['fields'] = array_filter($this->allComposableFields[$entity_type_id]['bundles'][$bundle_id]['fields'], fn($field) => $temp_count[$field['type']] > 2); } } // If there are no bundles, remove the entity type from the array. @@ -107,6 +167,17 @@ class CompileService implements CompileServiceInterface { return $this->allComposableFields; } + /** + * Returns string_long fields for a given entity type and bundle. + * + * @param string $entity_type_id + * The entity type ID. + * @param string $bundle_id + * The bundle ID. + * + * @return array + * An array of string_long fields. + */ public function getStringLongFields(string $entity_type_id, string $bundle_id): array { $long_text_fields = []; $all_fields = $this->entityFieldManager->getFieldDefinitions($entity_type_id, $bundle_id); @@ -126,21 +197,19 @@ class CompileService implements CompileServiceInterface { } /** - * Returns the configured composable fields for a given entity type and bundle. + * Returns the configured composable fields for a given entity type, bundle. * * @param string $entity_type_id * The entity type ID. * @param string $bundle_id * The bundle ID. * - * @return ComposableField[] + * @return \Drupal\component_fields\DTO\ComposableField[] * An array of configured composable fields. */ private function getConfiguredComposableFields(string $entity_type_id, string $bundle_id): array { - // @TODO the returning array must be validated: All the component names must be checked - // that fields actually exist: - // $field_definitions = $this->entityFieldManager->getFieldDefinitions($entity_type_id, $bundle_id); - + // @todo the returning array must be validated: All the component names must be checked + // that fields actually exist. $saved_config = $this->configFactory->get(ComponentFieldsBaseConfigForm::SETTINGS) ?->get('fields_config') ?? []; if (empty($saved_config)) { @@ -179,13 +248,18 @@ class CompileService implements CompileServiceInterface { return NULL; } + /** + * Compiles the fields for a given entity based on configur composable fields. + * + * @param \Drupal\Core\Entity\ContentEntityInterface $entity + * The entity to compile fields for. + */ public function compileFields(ContentEntityInterface $entity): void { $local_overrides = []; $entity_type_id = $entity->getEntityTypeId(); $bundle_id = $entity->bundle(); - // @TODO test the overrides - + // @todo test the overrides $override_field = $this->getOverrideField($entity_type_id, $bundle_id); if (!empty($override_field)) { $local_overrides = $entity->{$override_field}->value; diff --git a/src/CompileServiceInterface.php b/src/CompileServiceInterface.php index cffe7e227b2f2574aa13bba1a7b8b09ec8d7dc11..814be5c2be713e5c5b9436b51c360bbd571bde34 100644 --- a/src/CompileServiceInterface.php +++ b/src/CompileServiceInterface.php @@ -4,12 +4,15 @@ namespace Drupal\component_fields; use Drupal\Core\Entity\ContentEntityInterface; +/** + * Interface for compiling fields in composable entities. + */ interface CompileServiceInterface { /** * Updates the provided entity's compilable fields. * - * @param ContentEntityInterface $entity + * @param \Drupal\Core\Entity\ContentEntityInterface $entity * The entity to update. */ public function compileFields(ContentEntityInterface $entity): void; @@ -36,7 +39,7 @@ interface CompileServiceInterface { public function getAllComposableFields(): array; /** - * Returns all composable fields grouped by their type for a given entity type and bundle. + * Returns composable fields grouped by type for given entity type, bundle. * * @param string $entity_type_id * The entity type ID. diff --git a/src/ComponentFieldsCompilerBase.php b/src/ComponentFieldsCompilerBase.php index edd4aa384a7f741010534d6cbbf4fd53e4f9f317..4f54d25a9e4b0bac5237d47e946cf45370470c83 100644 --- a/src/ComponentFieldsCompilerBase.php +++ b/src/ComponentFieldsCompilerBase.php @@ -9,6 +9,12 @@ use Drupal\Component\Plugin\PluginBase; */ abstract class ComponentFieldsCompilerBase extends PluginBase implements ComponentFieldsCompilerInterface { + /** + * Returns the label of the plugin. + * + * @return string + * The label of the plugin. + */ public function label(): string { return (string) $this->pluginDefinition['label']; } diff --git a/src/ComponentFieldsCompilerInterface.php b/src/ComponentFieldsCompilerInterface.php index 885185123b30b4584844b465b51751c1516fdea7..5cc13aeb39072defc5b2b3cf1c234bbf4acb0bbd 100644 --- a/src/ComponentFieldsCompilerInterface.php +++ b/src/ComponentFieldsCompilerInterface.php @@ -5,23 +5,33 @@ namespace Drupal\component_fields; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Field\FieldDefinitionInterface; +/** + * Defines the interface for component fields compilers. + */ interface ComponentFieldsCompilerInterface { /** * Returns the translated plugin label. + * + * @return string + * The translated label of the plugin. */ public function label(): string; /** - * Compile the final field value. + * Compiles the final field value from the component values. * * @param mixed $component_1_value + * The value of the first component. * @param mixed $component_2_value - * @param FieldDefinitionInterface $field_definition - * @param ContentEntityInterface $entity + * The value of the second component. + * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition + * The field definition for the final field. + * @param \Drupal\Core\Entity\ContentEntityInterface $entity + * The entity containing the field. * * @return mixed - * The field final compiled value. + * The field's final compiled value. */ public function compile(mixed $component_1_value, mixed $component_2_value, FieldDefinitionInterface $field_definition, ContentEntityInterface $entity): mixed; diff --git a/src/ComponentFieldsCompilerPluginManager.php b/src/ComponentFieldsCompilerPluginManager.php index 832a4705ff0b85e9912754d4e66f5824ceaf1ffb..7606f72deaa48786c715431eebb6a8716bb26259 100644 --- a/src/ComponentFieldsCompilerPluginManager.php +++ b/src/ComponentFieldsCompilerPluginManager.php @@ -2,21 +2,36 @@ namespace Drupal\component_fields; +use Drupal\component_fields\Annotation\ComponentFieldsCompiler; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Plugin\DefaultPluginManager; -use Drupal\component_fields\Annotation\ComponentFieldsCompiler; -use Traversable; +/** + * Manages the Component Fields Compiler plugins. + */ final class ComponentFieldsCompilerPluginManager extends DefaultPluginManager { - public function __construct(Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) { - // @TODO implement a custom alter hook with context (for example: the entity itself). + /** + * Constructs a ComponentFieldsCompilerPluginManager object. + * + * @param \Traversable $namespaces + * The namespaces for the plugins. + * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend + * The cache backend to use. + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler service. + */ + public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) { + // @todo implement a custom alter hook with context (for example: the entity itself). parent::__construct('Plugin/ComponentFieldsCompiler', $namespaces, $module_handler, ComponentFieldsCompilerInterface::class, ComponentFieldsCompiler::class); $this->alterInfo('component_fields_info'); $this->setCacheBackend($cache_backend, 'component_fields_plugins'); } + /** + * {@inheritdoc} + */ public function getDefinitions(bool $exclude_multivalue = FALSE) { $all_definitions = parent::getDefinitions(); if ($exclude_multivalue) { diff --git a/src/DTO/ComposableField.php b/src/DTO/ComposableField.php index 3feb228024b1f12898e74dd071ccaeb161640190..c1994b911536814b91b62c1f0a229c8868d6f65f 100644 --- a/src/DTO/ComposableField.php +++ b/src/DTO/ComposableField.php @@ -2,16 +2,51 @@ namespace Drupal\component_fields\DTO; +/** + * Represents a composable field consisting of 2 component fields, final field. + */ class ComposableField { + /** + * The ID of the first component field. + * + * @var string + */ public readonly string $component1Id; + /** + * The ID of the second component field. + * + * @var string + */ public readonly string $component2Id; + /** + * The ID of the final composable field. + * + * @var string + */ public readonly string $finalId; + /** + * The ID of the compiler to use for this composable field. + * + * @var string + */ public readonly string $compiler; + /** + * Constructs a ComposableField object. + * + * @param string $component_1_id + * The ID of the first component field. + * @param string $component_2_id + * The ID of the second component field. + * @param string $final_id + * The ID of the final composable field. + * @param string $compiler + * The ID of the compiler to use. + */ public function __construct(string $component_1_id, string $component_2_id, string $final_id, string $compiler) { $this->component1Id = $component_1_id; $this->component2Id = $component_2_id; diff --git a/src/Form/BundlesForm.php b/src/Form/BundlesForm.php index 4a01bf49b9ca4d1efe458f59ead1c900e3677fe4..5542609a61abc3f478ce6caccca988055b08cc84 100644 --- a/src/Form/BundlesForm.php +++ b/src/Form/BundlesForm.php @@ -2,21 +2,36 @@ namespace Drupal\component_fields\Form; -use Drupal\Core\Form\FormStateInterface; use Drupal\component_fields\CompileService; +use Drupal\Core\Form\FormStateInterface; +/** + * Form for configuring which bundles should be enabled for component fields. + */ class BundlesForm extends ComponentFieldsBaseConfigForm { + /** + * The compile service for retrieving composable fields. + * + * @var \Drupal\component_fields\CompileService + */ protected CompileService $compileService; + /** + * {@inheritdoc} + */ public function getFormId() { return 'component_fields_enabled_bundles_config_form'; } + /** + * {@inheritdoc} + */ public function buildForm(array $form, FormStateInterface $form_state) { $enabled_bundles = $this->config(static::SETTINGS) ->get('enabled_bundles') ?? []; $all_composable_fields = $this->compileService->getAllComposableFields(); + if (empty($all_composable_fields)) { $form["header"] = [ '#markup' => $this->t("To be able to enable bundles, they need to have at least 3 fields of the same type."), @@ -27,6 +42,7 @@ class BundlesForm extends ComponentFieldsBaseConfigForm { $form["header"] = [ '#markup' => $this->t("Select the bundles that should be made available for component fields configuration. Only bundles with at least 3 fields of the same type can be enabled."), ]; + foreach ($all_composable_fields as $entity_type_id => $entity_type) { $form[$entity_type_id] = [ '#type' => 'details', @@ -46,11 +62,15 @@ class BundlesForm extends ComponentFieldsBaseConfigForm { return parent::buildForm($form, $form_state); } + /** + * {@inheritdoc} + */ public function submitForm(array &$form, FormStateInterface $form_state) { $config = $this->config(ComponentFieldsBaseConfigForm::SETTINGS); $values = $form_state->getValues(); $new_enabled_bundles = []; + foreach ($this->compileService->getAllComposableFields() as $entity_type_id => $data) { $enabled_bundles = isset($values[$entity_type_id]) ? array_keys(array_filter($values[$entity_type_id])) : []; if (!empty($enabled_bundles)) { @@ -90,6 +110,7 @@ class BundlesForm extends ComponentFieldsBaseConfigForm { ->set('fields_config', $fields_config) ->set('overrides', $overrides) ->save(); + parent::submitForm($form, $form_state); } diff --git a/src/Form/ComponentFieldsBaseConfigForm.php b/src/Form/ComponentFieldsBaseConfigForm.php index 3a71ee9945bd3794180dd04ad6c302cac4d53e05..c0ee52fa70466417c2ec8104740458c1c5673af3 100644 --- a/src/Form/ComponentFieldsBaseConfigForm.php +++ b/src/Form/ComponentFieldsBaseConfigForm.php @@ -11,20 +11,51 @@ use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\ConfigFormBase; use Symfony\Component\DependencyInjection\ContainerInterface; +/** + * Base class for component fields configuration forms. + */ abstract class ComponentFieldsBaseConfigForm extends ConfigFormBase { public const SETTINGS = 'component_fields.settings'; + /** + * The compile service for retrieving composable fields. + * + * @var \Drupal\component_fields\CompileService + */ protected CompileService $compileService; + /** + * The plugin manager for component fields compilers. + * + * @var \Drupal\component_fields\ComponentFieldsCompilerPluginManager + */ protected ComponentFieldsCompilerPluginManager $componentFieldsPluginManager; + /** + * The entity type manager service. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ protected EntityTypeManagerInterface $entityTypeManager; + /** + * The entity type bundle info service. + * + * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface + */ protected EntityTypeBundleInfoInterface $entityTypeBundleInfo; + /** + * The entity field manager service. + * + * @var \Drupal\Core\Entity\EntityFieldManagerInterface + */ protected EntityFieldManagerInterface $entityFieldManager; + /** + * {@inheritdoc} + */ public static function create(ContainerInterface $container) { return new static( $container->get('config.factory'), @@ -32,11 +63,34 @@ abstract class ComponentFieldsBaseConfigForm extends ConfigFormBase { $container->get('plugin.manager.component_fields_compiler'), $container->get('entity_type.manager'), $container->get('entity_type.bundle.info'), - $container->get('entity_field.manager'), + $container->get('entity_field.manager') ); } - public function __construct(ConfigFactoryInterface $config_factory, CompileService $compile_service, ComponentFieldsCompilerPluginManager $compiler_manager, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityFieldManagerInterface $entity_field_manager) { + /** + * Constructs the ComponentFieldsBaseConfigForm. + * + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The configuration factory. + * @param \Drupal\component_fields\CompileService $compile_service + * The compile service. + * @param \Drupal\component_fields\ComponentFieldsCompilerPluginManager $compiler_manager + * The compiler plugin manager. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. + * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info + * The entity type bundle info. + * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager + * The entity field manager. + */ + public function __construct( + ConfigFactoryInterface $config_factory, + CompileService $compile_service, + ComponentFieldsCompilerPluginManager $compiler_manager, + EntityTypeManagerInterface $entity_type_manager, + EntityTypeBundleInfoInterface $entity_type_bundle_info, + EntityFieldManagerInterface $entity_field_manager, + ) { parent::__construct($config_factory); $this->compileService = $compile_service; $this->componentFieldsPluginManager = $compiler_manager; @@ -45,10 +99,11 @@ abstract class ComponentFieldsBaseConfigForm extends ConfigFormBase { $this->entityFieldManager = $entity_field_manager; } + /** + * {@inheritdoc} + */ protected function getEditableConfigNames() { return [static::SETTINGS]; } - - } diff --git a/src/Form/FieldsForm.php b/src/Form/FieldsForm.php index 3544d5f4231be5b8613ad23f4f5849187ffdee75..c578aba9b1748b59d47535f66200ff1165cf4328 100644 --- a/src/Form/FieldsForm.php +++ b/src/Form/FieldsForm.php @@ -2,24 +2,63 @@ namespace Drupal\component_fields\Form; +use Drupal\component_fields\CompileService; +use Drupal\component_fields\ComponentFieldsCompilerPluginManager; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Entity\EntityFieldManagerInterface; use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormStateInterface; -use Drupal\component_fields\CompileService; -use Drupal\component_fields\ComponentFieldsCompilerPluginManager; +/** + * Form for managing component fields configuration. + */ class FieldsForm extends ComponentFieldsBaseConfigForm { + /** + * List of enabled bundles for component fields. + * + * @var array + */ protected array $enabledBundles; + /** + * Indicates whether this is the initial build of the form. + * + * @var bool + */ protected bool $isInitialBuild = TRUE; + /** + * Saved configuration for fields. + * + * @var array + */ protected array $savedFieldsConfig = []; + /** + * Name of the final field for triads, if applicable. + * + * @var string|null + */ private ?string $triadsFinalFieldName = NULL; + /** + * Constructs a FieldsForm object. + * + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The configuration factory service. + * @param \Drupal\component_fields\CompileService $compile_service + * The compile service for processing fields. + * @param \Drupal\component_fields\ComponentFieldsCompilerPluginManager $compiler_manager + * The compiler manager for component fields. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager service. + * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info + * The entity type bundle info service. + * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager + * The entity field manager service. + */ public function __construct(ConfigFactoryInterface $config_factory, CompileService $compile_service, ComponentFieldsCompilerPluginManager $compiler_manager, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityFieldManagerInterface $entity_field_manager) { parent::__construct($config_factory, $compile_service, $compiler_manager, $entity_type_manager, $entity_type_bundle_info, $entity_field_manager); $config = $this->config(ComponentFieldsBaseConfigForm::SETTINGS); @@ -27,10 +66,27 @@ class FieldsForm extends ComponentFieldsBaseConfigForm { $this->savedFieldsConfig = $config->get('fields_config') ?? []; } + /** + * Returns the form ID for the component fields configuration form. + * + * @return string + * The form ID. + */ public function getFormId() { return 'component_fields_fields_config_form'; } + /** + * Builds the configuration form for component fields. + * + * @param array $form + * The form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return array + * The modified form array. + */ public function buildForm(array $form, FormStateInterface $form_state) { if (empty($this->enabledBundles)) { $form['header'] = [ @@ -58,6 +114,18 @@ class FieldsForm extends ComponentFieldsBaseConfigForm { return parent::buildForm($form, $form_state); } + /** + * Builds the triad elements for the specified entity type and bundle. + * + * @param array &$form + * The form array, passed by reference. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * @param string $entity_type_id + * The entity type ID. + * @param string $bundle_id + * The bundle ID. + */ public function buildTriads(array &$form, FormStateInterface $form_state, string $entity_type_id, string $bundle_id): void { $saved_triads = $this->getSavedTriads($entity_type_id, $bundle_id); $saved_triads_count = count($saved_triads); @@ -128,6 +196,17 @@ class FieldsForm extends ComponentFieldsBaseConfigForm { ]; } + /** + * Retrieves available compiler options based on the entity type and bundle. + * + * @param string $entity_type_id + * The entity type ID. + * @param string $bundle_id + * The bundle ID. + * + * @return array + * An associative array of compiler options. + */ private function getAvailableCompilerOptions($entity_type_id, $bundle_id): array { $options = []; if ($this->triadsFinalFieldName) { @@ -149,9 +228,25 @@ class FieldsForm extends ComponentFieldsBaseConfigForm { return ['' => '-'] + $options; } + /** + * Retrieves available field options for a specific triad component. + * + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * @param string $entity_type_id + * The entity type ID. + * @param string $bundle_id + * The bundle ID. + * @param int $triad_index + * The index of the triad. + * @param string $component + * The component name (e.g., 'final', 'component_1', 'component_2'). + * + * @return array + * An associative array of field options. + */ private function getAvailableFieldOptions(FormStateInterface $form_state, $entity_type_id, $bundle_id, $triad_index, $component): array { - // @TODO Maybe check also for cardinality. - + // @todo Maybe check also for cardinality. // First get all the fields that are available for the current entity type // and bundle. $bundle_fields_groupped = $this->compileService->getAllComposableFieldsGroupped($entity_type_id, $bundle_id); @@ -237,6 +332,19 @@ class FieldsForm extends ComponentFieldsBaseConfigForm { return ['' => '-'] + $bundle_fields_flat; } + /** + * Finds the field type for a given field name. + * + * @param string $field_name + * The name of the field. + * @param string $entity_type_id + * The entity type ID. + * @param string $bundle_id + * The bundle ID. + * + * @return string|null + * The field type if found, NULL otherwise. + */ private function findFieldType($field_name, $entity_type_id, $bundle_id): ?string { $groups = $this->compileService->getAllComposableFieldsGroupped($entity_type_id, $bundle_id); foreach ($groups as $group_field_type => $fields_in_group) { @@ -247,6 +355,21 @@ class FieldsForm extends ComponentFieldsBaseConfigForm { return NULL; } + /** + * Builds a triad fieldset for a specific entity type and bundle. + * + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * @param string $entity_type_id + * The entity type ID. + * @param string $bundle_id + * The bundle ID. + * @param int $triad_index + * The index of the triad. + * + * @return array + * A render array representing the triad fieldset. + */ private function getTriad(FormStateInterface $form_state, $entity_type_id, $bundle_id, $triad_index): array { $parent_wrapper_id = "$entity_type_id-$bundle_id-fieldset-wrapper"; @@ -326,6 +449,14 @@ class FieldsForm extends ComponentFieldsBaseConfigForm { return $triad; } + /** + * Callback for adding a new triad to the form. + * + * @param array $form + * The form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ public function addSubmitCallback(array &$form, FormStateInterface $form_state): void { $trigger = $form_state->getTriggeringElement(); $parents = $trigger['#array_parents']; @@ -336,6 +467,14 @@ class FieldsForm extends ComponentFieldsBaseConfigForm { $form_state->setRebuild(); } + /** + * Callback for removing a triad from the form. + * + * @param array $form + * The form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ public function removeSubmitCallback(array &$form, FormStateInterface $form_state): void { $trigger = $form_state->getTriggeringElement(); $parents = $trigger['#array_parents']; @@ -344,6 +483,17 @@ class FieldsForm extends ComponentFieldsBaseConfigForm { $form_state->setRebuild(); } + /** + * AJAX callback to handle the addition and removal of triads. + * + * @param array $form + * The form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return array + * A render array for the triads fieldset. + */ public function addRemoveTriadAjaxCallback(array &$form, FormStateInterface $form_state): array { $trigger = $form_state->getTriggeringElement(); $parents = $trigger['#array_parents']; @@ -351,6 +501,17 @@ class FieldsForm extends ComponentFieldsBaseConfigForm { return $form[$entity_type_id][$bundle_id]['triads_fieldset']; } + /** + * AJAX callback to update the available fields. + * + * @param array $form + * The form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return array + * A render array for the triads fieldset. + */ public function selectFieldAjaxCallback(array &$form, FormStateInterface $form_state): array { $trigger = $form_state->getTriggeringElement(); $parents = $trigger['#array_parents']; @@ -358,6 +519,17 @@ class FieldsForm extends ComponentFieldsBaseConfigForm { return $form[$entity_type_id][$bundle_id]['triads_fieldset']; } + /** + * Retrieves saved triads for a specific entity type and bundle. + * + * @param string $entity_type_id + * The entity type ID. + * @param string $bundle_id + * The bundle ID. + * + * @return array + * An array of saved triads. + */ private function getSavedTriads($entity_type_id, $bundle_id): array { if (isset($this->savedFieldsConfig[$entity_type_id][$bundle_id])) { return $this->savedFieldsConfig[$entity_type_id][$bundle_id]; @@ -365,6 +537,19 @@ class FieldsForm extends ComponentFieldsBaseConfigForm { return []; } + /** + * Gets the current triads from the form state. + * + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * @param string $entity_type_id + * The entity type ID. + * @param string $bundle_id + * The bundle ID. + * + * @return array|null + * An array of current triads or NULL if none exist. + */ private function getCurrentTriads(FormStateInterface $form_state, $entity_type_id, $bundle_id): ?array { $storage = $form_state->getStorage(); if (!isset($storage['current_triads'][$entity_type_id])) { @@ -377,6 +562,18 @@ class FieldsForm extends ComponentFieldsBaseConfigForm { return $storage['current_triads'][$entity_type_id][$bundle_id]; } + /** + * Adds a new triad to the current triads in the form state. + * + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * @param string $entity_type_id + * The entity type ID. + * @param string $bundle_id + * The bundle ID. + * @param int $triad_index + * The index of the triad to add. + */ private function addTriad(FormStateInterface $form_state, $entity_type_id, $bundle_id, $triad_index): void { $storage = $form_state->getStorage(); if (!isset($storage['current_triads'][$entity_type_id])) { @@ -392,6 +589,18 @@ class FieldsForm extends ComponentFieldsBaseConfigForm { $form_state->setStorage($storage); } + /** + * Removes a triad from the current triads in the form state. + * + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * @param string $entity_type_id + * The entity type ID. + * @param string $bundle_id + * The bundle ID. + * @param int $triad_index + * The index of the triad to remove. + */ private function removeTriad(FormStateInterface $form_state, $entity_type_id, $bundle_id, $triad_index): void { $storage = $form_state->getStorage(); if (!in_array($triad_index, $storage['current_triads'][$entity_type_id][$bundle_id], TRUE)) { @@ -408,6 +617,18 @@ class FieldsForm extends ComponentFieldsBaseConfigForm { $form_state->setStorage($storage); } + /** + * Validates the form data to ensure all required fields are filled. + * + * This method checks that the final field, component fields, + * are set correctly for each triad. If any of these fields are missing, + * an error is added to the form state. + * + * @param array $form + * The form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ public function validateForm(array &$form, FormStateInterface $form_state) { // Only run the validation if the form is getting saved. if ($form_state->getTriggeringElement()['#name'] !== 'op') { @@ -428,7 +649,6 @@ class FieldsForm extends ComponentFieldsBaseConfigForm { $component_1 = $triad["triad_wrapper"]["component_1"] ?? NULL; $component_2 = $triad["triad_wrapper"]["component_2"] ?? NULL; $default_compiler = $triad["triad_wrapper"]["default_compiler"] ?? NULL; - // If all have a non-empty value, or none of them has a value, then continue. if (($final && $component_1 && $component_2 && $default_compiler) || (!$final && !$component_1 && !$component_2 && !$default_compiler)) { continue; } @@ -450,6 +670,22 @@ class FieldsForm extends ComponentFieldsBaseConfigForm { } } + /** + * Submits the form and saves the configuration for the enabled bundles. + * + * This method processes the form values, organizes them into a structured + * configuration array, and removes any empty triads. It updates + * for each entity type and bundle based on the submitted data. + * + * @param array $form + * The form array containing the form structure. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form, including the submitted values. + * + * @return void + * This method does not return a value. It modifies the configuration array + * for the enabled bundles directly. + */ public function submitForm(array &$form, FormStateInterface $form_state) { $config = []; $values = $form_state->getValues(); @@ -465,7 +701,6 @@ class FieldsForm extends ComponentFieldsBaseConfigForm { } $config[$entity_type_id][$bundle_id][$i][$component] = $value; } - // If all the elements of the $config[$entity_type_id][$bundle_id][$i] array are empty, then remove the array. if (empty(array_filter($config[$entity_type_id][$bundle_id][$i]))) { unset($config[$entity_type_id][$bundle_id][$i]); } @@ -506,4 +741,4 @@ class FieldsForm extends ComponentFieldsBaseConfigForm { } -// @TODO when changing any triad, check if there is content potentially affected and notify the user. +// @todo when changing any triad, check if there is content potentially affected and notify the user. diff --git a/src/Form/OverrideFieldsForm.php b/src/Form/OverrideFieldsForm.php index 4de5a45b2b5b3a2fb7fb0b4afeb6f8356c801ea5..002bfb8d7c1a5c8c5e24157e7cdb6cd63f72450a 100644 --- a/src/Form/OverrideFieldsForm.php +++ b/src/Form/OverrideFieldsForm.php @@ -2,33 +2,88 @@ namespace Drupal\component_fields\Form; +use Drupal\component_fields\CompileService; +use Drupal\component_fields\ComponentFieldsCompilerPluginManager; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Entity\EntityFieldManagerInterface; use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormStateInterface; -use Drupal\component_fields\CompileService; -use Drupal\component_fields\ComponentFieldsCompilerPluginManager; +/** + * Provides a form for overriding field compilation settings. + */ class OverrideFieldsForm extends ComponentFieldsBaseConfigForm { + /** + * The enabled bundles for component fields. + * + * @var array + */ protected array $enabledBundles; + /** + * The saved fields configuration. + * + * @var array + */ protected array $savedFieldsConfig = []; + /** + * The available string_long fields for overrides. + * + * @var array + */ protected array $availableStringLongFields; + /** + * {@inheritdoc} + */ public function getFormId() { return 'component_fields_override_fields_config_form'; } - public function __construct(ConfigFactoryInterface $config_factory, CompileService $compile_service, ComponentFieldsCompilerPluginManager $compiler_manager, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityFieldManagerInterface $entity_field_manager) { + /** + * Constructs the OverrideFieldsForm. + * + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The configuration factory. + * @param \Drupal\component_fields\CompileService $compile_service + * The compile service. + * @param \Drupal\component_fields\ComponentFieldsCompilerPluginManager $compiler_manager + * The component fields compiler plugin manager. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager service. + * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info + * The entity type bundle info service. + * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager + * The entity field manager service. + */ + public function __construct( + ConfigFactoryInterface $config_factory, + CompileService $compile_service, + ComponentFieldsCompilerPluginManager $compiler_manager, + EntityTypeManagerInterface $entity_type_manager, + EntityTypeBundleInfoInterface $entity_type_bundle_info, + EntityFieldManagerInterface $entity_field_manager, + ) { parent::__construct($config_factory, $compile_service, $compiler_manager, $entity_type_manager, $entity_type_bundle_info, $entity_field_manager); $config = $this->config(ComponentFieldsBaseConfigForm::SETTINGS); $this->enabledBundles = $config->get('enabled_bundles') ?? []; $this->savedFieldsConfig = $config->get('fields_config') ?? []; } + /** + * Builds the override fields form. + * + * @param array $form + * The form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. + * + * @return array + * The form array. + */ public function buildForm(array $form, FormStateInterface $form_state) { if (empty($this->enabledBundles)) { $form['header'] = [ @@ -42,11 +97,7 @@ class OverrideFieldsForm extends ComponentFieldsBaseConfigForm { $form['#tree'] = TRUE; $form['header'] = [ - '#markup' => $this->t('If you want to be able to override the field - compilation per entity, select a field to store the overrides. The bundle - must have at least one string_long field available, that hasn\'t been - used in any component field configuration. Only the bundles that have - such a field, and at least one component field configuration, are listed.'), + '#markup' => $this->t("If you want to be able to override the field compilation per entity, select a field to store the overrides. The bundle must have at least one string_long field available, that hasn\'t been used in any component field configuration. Only the bundles that have such a field, and at least one component field configuration, are listed."), ]; $this->getStringLongFields(); @@ -91,6 +142,14 @@ class OverrideFieldsForm extends ComponentFieldsBaseConfigForm { return parent::buildForm($form, $form_state); } + /** + * Submits the override fields form. + * + * @param array $form + * The form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. + */ public function submitForm(array &$form, FormStateInterface $form_state) { $override_fields = []; $values = $form_state->getValues(); @@ -113,6 +172,12 @@ class OverrideFieldsForm extends ComponentFieldsBaseConfigForm { parent::submitForm($form, $form_state); } + /** + * Updates the widgets for the specified override fields. + * + * @param array $override_fields + * The override fields to update. + */ private function updateWidgets(array $override_fields) { foreach ($override_fields as $entity_type_id => $bundles) { foreach ($bundles as $bundle_id => $field_name) { @@ -140,6 +205,9 @@ class OverrideFieldsForm extends ComponentFieldsBaseConfigForm { } } + /** + * Retrieves string_long fields for enabled bundles. + */ private function getStringLongFields(): void { foreach ($this->enabledBundles as $entity_type_id => $bundles) { foreach ($bundles as $bundle_id) { diff --git a/src/Plugin/ComponentFieldsCompiler/Component1.php b/src/Plugin/ComponentFieldsCompiler/Component1.php index 7606f6f32eccc9567f72c566ae479e4daae9ecc8..ff2372144a890af6c97da0cffab16169d8eee4c3 100644 --- a/src/Plugin/ComponentFieldsCompiler/Component1.php +++ b/src/Plugin/ComponentFieldsCompiler/Component1.php @@ -2,11 +2,13 @@ namespace Drupal\component_fields\Plugin\ComponentFieldsCompiler; +use Drupal\component_fields\ComponentFieldsCompilerBase; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Field\FieldDefinitionInterface; -use Drupal\component_fields\ComponentFieldsCompilerBase; /** + * Provides a compiler for Component 1. + * * @ComponentFieldsCompiler( * id = "component_1", * label = @Translation("Component 1"), @@ -16,6 +18,21 @@ use Drupal\component_fields\ComponentFieldsCompilerBase; */ final class Component1 extends ComponentFieldsCompilerBase { + /** + * Compiles the value of Component 1. + * + * @param mixed $component_1_value + * The value of Component 1. + * @param mixed $component_2_value + * The value of Component 2 (not used in this implementation). + * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition + * The field definition associated with the component. + * @param \Drupal\Core\Entity\ContentEntityInterface $entity + * The entity being processed. + * + * @return mixed + * The processed value of Component 1. + */ public function compile(mixed $component_1_value, mixed $component_2_value, FieldDefinitionInterface $field_definition, ContentEntityInterface $entity): mixed { return $component_1_value; } diff --git a/src/Plugin/ComponentFieldsCompiler/Component1WithFallback2.php b/src/Plugin/ComponentFieldsCompiler/Component1WithFallback2.php index c10de7053a9e0eb9d59e1bb46603f73c0289e337..5c1cc839cd87942308a260e9829198d7058b5080 100644 --- a/src/Plugin/ComponentFieldsCompiler/Component1WithFallback2.php +++ b/src/Plugin/ComponentFieldsCompiler/Component1WithFallback2.php @@ -7,6 +7,8 @@ use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Field\FieldDefinitionInterface; /** + * Provides a compiler that uses Component 1 with a fallback to Component 2. + * * @ComponentFieldsCompiler( * id = "component_1_fallback_2", * label = @Translation("Component 1 with fallback 2"), @@ -16,6 +18,21 @@ use Drupal\Core\Field\FieldDefinitionInterface; */ class Component1WithFallback2 extends ComponentFieldsCompilerBase { + /** + * Compiles the value by using Component 1 with a fallback to Component 2. + * + * @param mixed $component_1_value + * The value of Component 1. + * @param mixed $component_2_value + * The value of Component 2. + * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition + * The field definition associated with the component. + * @param \Drupal\Core\Entity\ContentEntityInterface $entity + * The entity being processed. + * + * @return mixed + * The value of Component 1 if it exists. + */ public function compile(mixed $component_1_value, mixed $component_2_value, FieldDefinitionInterface $field_definition, ContentEntityInterface $entity): mixed { if ($component_1_value) { return $component_1_value; diff --git a/src/Plugin/ComponentFieldsCompiler/Component2.php b/src/Plugin/ComponentFieldsCompiler/Component2.php index 4f7efa179bdbd68ef0d8e362e2f08691c70355de..8e0e0c5ea99ced52b5b9874a510573ad129398be 100644 --- a/src/Plugin/ComponentFieldsCompiler/Component2.php +++ b/src/Plugin/ComponentFieldsCompiler/Component2.php @@ -2,11 +2,13 @@ namespace Drupal\component_fields\Plugin\ComponentFieldsCompiler; +use Drupal\component_fields\ComponentFieldsCompilerBase; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Field\FieldDefinitionInterface; -use Drupal\component_fields\ComponentFieldsCompilerBase; /** + * Provides a compiler that retains the value of Component 2. + * * @ComponentFieldsCompiler( * id = "component_2", * label = @Translation("Component 2"), @@ -16,6 +18,21 @@ use Drupal\component_fields\ComponentFieldsCompilerBase; */ final class Component2 extends ComponentFieldsCompilerBase { + /** + * Compiles the value by returning the value of Component 2. + * + * @param mixed $component_1_value + * The value of Component 1 (not used in this implementation). + * @param mixed $component_2_value + * The value of Component 2. + * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition + * The field definition associated with the component. + * @param \Drupal\Core\Entity\ContentEntityInterface $entity + * The entity being processed. + * + * @return mixed + * The value of Component 2. + */ public function compile(mixed $component_1_value, mixed $component_2_value, FieldDefinitionInterface $field_definition, ContentEntityInterface $entity): mixed { return $component_2_value; } diff --git a/src/Plugin/ComponentFieldsCompiler/Component2WithFallback1.php b/src/Plugin/ComponentFieldsCompiler/Component2WithFallback1.php index 58a12ffd3d1df102014074bed71454ba324d8220..3c5c42b118d5d63c82b24dd5cc78d315e5a16434 100644 --- a/src/Plugin/ComponentFieldsCompiler/Component2WithFallback1.php +++ b/src/Plugin/ComponentFieldsCompiler/Component2WithFallback1.php @@ -7,6 +7,8 @@ use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Field\FieldDefinitionInterface; /** + * Provides a compiler that retains value of C2 with fallback to C1. + * * @ComponentFieldsCompiler( * id = "component_2_fallback_1", * label = @Translation("Component 2 with fallback 1"), @@ -16,6 +18,21 @@ use Drupal\Core\Field\FieldDefinitionInterface; */ class Component2WithFallback1 extends ComponentFieldsCompilerBase { + /** + * Compiles the value by returning Component 2 or falling back to Component 1. + * + * @param mixed $component_1_value + * The value of Component 1. + * @param mixed $component_2_value + * The value of Component 2. + * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition + * The field definition associated with the component. + * @param \Drupal\Core\Entity\ContentEntityInterface $entity + * The entity being processed. + * + * @return mixed + * The value of C2 if it exists, otherwise the value of C1. + */ public function compile(mixed $component_1_value, mixed $component_2_value, FieldDefinitionInterface $field_definition, ContentEntityInterface $entity): mixed { if ($component_2_value) { return $component_2_value; diff --git a/src/Plugin/ComponentFieldsCompiler/EmptyValue.php b/src/Plugin/ComponentFieldsCompiler/EmptyValue.php index 86a995eed842bd9bce606aacb0d2b358b2b534b7..58f31c4d71221df8af8c140a8850743d516f1bd1 100644 --- a/src/Plugin/ComponentFieldsCompiler/EmptyValue.php +++ b/src/Plugin/ComponentFieldsCompiler/EmptyValue.php @@ -2,11 +2,13 @@ namespace Drupal\component_fields\Plugin\ComponentFieldsCompiler; +use Drupal\component_fields\ComponentFieldsCompilerBase; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Field\FieldDefinitionInterface; -use Drupal\component_fields\ComponentFieldsCompilerBase; /** + * Provides a compiler that sets an empty value. + * * @ComponentFieldsCompiler( * id = "empty_value", * label = @Translation("Empty value"), @@ -16,9 +18,23 @@ use Drupal\component_fields\ComponentFieldsCompilerBase; */ class EmptyValue extends ComponentFieldsCompilerBase { + /** + * Compiles the value by returning NULL. + * + * @param mixed $component_1_value + * The value of Component 1 (unused). + * @param mixed $component_2_value + * The value of Component 2 (unused). + * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition + * The field definition associated with the component. + * @param \Drupal\Core\Entity\ContentEntityInterface $entity + * The entity being processed. + * + * @return null + * Always returns NULL. + */ public function compile(mixed $component_1_value, mixed $component_2_value, FieldDefinitionInterface $field_definition, ContentEntityInterface $entity): mixed { return NULL; } } - diff --git a/src/Plugin/ComponentFieldsCompiler/Merge.php b/src/Plugin/ComponentFieldsCompiler/Merge.php index 7d102c63f5dddf0a4518273cf852f4a1a9403d60..bc80ba2698d8e28c838e0fa63443c11b39796617 100644 --- a/src/Plugin/ComponentFieldsCompiler/Merge.php +++ b/src/Plugin/ComponentFieldsCompiler/Merge.php @@ -2,11 +2,16 @@ namespace Drupal\component_fields\Plugin\ComponentFieldsCompiler; +use Drupal\component_fields\ComponentFieldsCompilerBase; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Field\FieldDefinitionInterface; -use Drupal\component_fields\ComponentFieldsCompilerBase; /** + * Merges the values from both components. + * + * This compiler takes values from 2 components, merges them into single array, + * ensuring that duplicate values are removed. + * * @ComponentFieldsCompiler( * id = "merge", * label = @Translation("Merge"), @@ -16,6 +21,21 @@ use Drupal\component_fields\ComponentFieldsCompilerBase; */ final class Merge extends ComponentFieldsCompilerBase { + /** + * Compiles the values by merging them into a single array. + * + * @param mixed $component_1_value + * The value from Component 1, expected to be an array. + * @param mixed $component_2_value + * The value from Component 2, expected to be an array. + * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition + * The field definition associated with the components. + * @param \Drupal\Core\Entity\ContentEntityInterface $entity + * The entity being processed. + * + * @return array + * An array containing the merged values, with duplicates removed. + */ public function compile(mixed $component_1_value, mixed $component_2_value, FieldDefinitionInterface $field_definition, ContentEntityInterface $entity): array { if (count($component_1_value) && count($component_2_value)) { $merged = array_merge($component_1_value, $component_2_value); @@ -32,7 +52,16 @@ final class Merge extends ComponentFieldsCompilerBase { } } - private function dedupe($merged): array { + /** + * Removes duplicate values from the merged array. + * + * @param array $merged + * The array containing merged values. + * + * @return array + * An array with duplicates removed. + */ + private function dedupe(array $merged): array { $temp = []; $deduped = []; foreach ($merged as $i => $value_construct) { diff --git a/src/Plugin/Field/FieldWidget/ComponentFieldsWidget.php b/src/Plugin/Field/FieldWidget/ComponentFieldsWidget.php index a407bef01a91a97db3c66a8fa4084b4cc334a3ff..358c4610ca0a78568023930e1cd111356d368d0e 100644 --- a/src/Plugin/Field/FieldWidget/ComponentFieldsWidget.php +++ b/src/Plugin/Field/FieldWidget/ComponentFieldsWidget.php @@ -2,19 +2,18 @@ namespace Drupal\component_fields\Plugin\Field\FieldWidget; +use Drupal\component_fields\ComponentFieldsCompilerPluginManager; use Drupal\component_fields\Form\ComponentFieldsBaseConfigForm; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Entity\EntityFieldManagerInterface; -use Drupal\Core\Field\Annotation\FieldWidget; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\WidgetBase; use Drupal\Core\Form\FormStateInterface; -use Drupal\component_fields\ComponentFieldsCompilerPluginManager; use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Widget for override fields. + * Widget for overriding fields in component fields configuration. * * @FieldWidget( * id = "component_fields_override_widget", @@ -26,17 +25,67 @@ use Symfony\Component\DependencyInjection\ContainerInterface; */ class ComponentFieldsWidget extends WidgetBase { + /** + * Entity field manager service. + * + * @var \Drupal\Core\Entity\EntityFieldManagerInterface + */ private EntityFieldManagerInterface $entityFieldManager; + /** + * Compilers that do not support multi-value. + * + * @var array + */ private array $compilersWithoutMultivalue; + /** + * Compilers that include multi-value options. + * + * @var array + */ private array $compilersIncludingMultivalue; + /** + * Default compilers for each bundle. + * + * @var array + */ private array $defaultCompilersForBundle = []; - public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, EntityFieldManagerInterface $entity_field_manager, ComponentFieldsCompilerPluginManager $componentFieldsPluginManager, ConfigFactoryInterface $config_factory) { + /** + * ComponentFieldsWidget constructor. + * + * @param string $plugin_id + * The plugin ID. + * @param array $plugin_definition + * The plugin definition. + * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition + * The field definition. + * @param array $settings + * The widget settings. + * @param array $third_party_settings + * Third-party settings. + * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager + * The entity field manager service. + * @param \Drupal\component_fields\ComponentFieldsCompilerPluginManager $componentFieldsPluginManager + * The component fields compiler plugin manager. + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The configuration factory service. + */ + public function __construct( + $plugin_id, + $plugin_definition, + FieldDefinitionInterface $field_definition, + array $settings, + array $third_party_settings, + EntityFieldManagerInterface $entity_field_manager, + ComponentFieldsCompilerPluginManager $componentFieldsPluginManager, + ConfigFactoryInterface $config_factory, + ) { parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings); $this->entityFieldManager = $entity_field_manager; + $available_compiler_definitions = $componentFieldsPluginManager->getDefinitions(); foreach ($available_compiler_definitions as $definition) { if (!$definition["multivalue_only"]) { @@ -57,6 +106,9 @@ class ComponentFieldsWidget extends WidgetBase { } } + /** + * {@inheritdoc} + */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { return new static( $plugin_id, @@ -66,21 +118,37 @@ class ComponentFieldsWidget extends WidgetBase { $configuration['third_party_settings'], $container->get('entity_field.manager'), $container->get('plugin.manager.component_fields_compiler'), - $container->get('config.factory'), + $container->get('config.factory') ); } + /** + * {@inheritdoc} + */ public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { $entity = $items->getEntity(); $entity_type_id = $entity->getEntityTypeId(); $bundle = $entity->bundle(); - $overrides = isset($items[$delta]->value) ? $items[$delta]->value : NULL; + $overrides = $items[$delta]->value ?? NULL; $checkboxes = $this->getBundleFormElements($entity_type_id, $bundle, $overrides); $element['value'] = $element + $checkboxes; return $element; } + /** + * Builds form elements for the fields in the specified bundle. + * + * @param string $entity_type_id + * The entity type ID. + * @param string $bundle + * The bundle ID. + * @param string|null $overrides + * The JSON encoded overrides, if any. + * + * @return array + * An array of form elements for the bundle fields. + */ private function getBundleFormElements(string $entity_type_id, string $bundle, ?string $overrides): array { $field_definitions = $this->entityFieldManager->getFieldDefinitions($entity_type_id, $bundle); $fields_options = []; @@ -95,8 +163,7 @@ class ComponentFieldsWidget extends WidgetBase { $checkboxes = []; foreach ($fields_options as $field_id => $field_label) { $field_definition = $field_definitions[$field_id]; - $cardinality = $field_definition->getFieldStorageDefinition() - ->getCardinality(); + $cardinality = $field_definition->getFieldStorageDefinition()->getCardinality(); if (!empty($this->defaultCompilersForBundle[$field_id])) { $default_for_field = $this->defaultCompilersForBundle[$field_id]; @@ -116,7 +183,16 @@ class ComponentFieldsWidget extends WidgetBase { return $checkboxes; } - private function generateOptions($cardinality): array { + /** + * Generates options based on the field cardinality. + * + * @param int $cardinality + * The cardinality of the field. + * + * @return array + * An array of options for the field widget. + */ + private function generateOptions(int $cardinality): array { if ($cardinality === 1) { $options = $this->compilersWithoutMultivalue; } @@ -129,8 +205,21 @@ class ComponentFieldsWidget extends WidgetBase { return $options; } + /** + * Processes and sanitizes form values before saving. + * + * @param array $values + * The values submitted in the form. + * @param array $form + * The form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. + * + * @return string|null + * A JSON encoded string of filtered values, or NULL if no values are valid. + */ public function massageFormValues(array $values, array $form, FormStateInterface $form_state) { - $filtered = array_filter($values[0]["value"], function($selected_value, $field_name) { + $filtered = array_filter($values[0]["value"], function ($selected_value, $field_name) { $is_empty = empty($selected_value); $is_explicitly_default = ($selected_value === 'default'); $is_implicitly_default = (!empty($this->defaultCompilersForBundle[$field_name]) && $selected_value === $this->defaultCompilersForBundle[$field_name]);