From a3b983a44de65ceb4f0c0347136cca29b3f03349 Mon Sep 17 00:00:00 2001
From: Dieter Holvoet <dieter.holvoet@gmail.com>
Date: Mon, 9 Sep 2024 11:26:03 +0200
Subject: [PATCH 1/4] Rewrite module to use default field storage

---
 image_field_caption.info.yml                  |   3 +-
 image_field_caption.install                   | 376 +++++++++-------
 image_field_caption.module                    | 307 +++----------
 image_field_caption.services.yml              |   7 -
 src/ImageCaptionItem.php                      | 104 ++++-
 src/ImageCaptionStorage.php                   | 425 ------------------
 .../FieldFormatter/ImageCaptionFormatter.php  |   2 +
 7 files changed, 355 insertions(+), 869 deletions(-)
 delete mode 100644 image_field_caption.services.yml
 delete mode 100644 src/ImageCaptionStorage.php

diff --git a/image_field_caption.info.yml b/image_field_caption.info.yml
index 17d7bf9..e04a8e9 100644
--- a/image_field_caption.info.yml
+++ b/image_field_caption.info.yml
@@ -2,4 +2,5 @@ name: Image Field Caption
 description: 'Provides a caption textarea for image fields.'
 package: Other
 type: module
-core_version_requirement: ^9 || ^10
+core_version_requirement: ^9.2 || ^10
+php: 7.1
diff --git a/image_field_caption.install b/image_field_caption.install
index 92e9220..63a974b 100644
--- a/image_field_caption.install
+++ b/image_field_caption.install
@@ -1,172 +1,222 @@
 <?php
 
+use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
+use Drupal\Core\Entity\Sql\SqlContentEntityStorageException;
+
 /**
- * @file
- * Install, update and uninstall functions for the image caption module.
+ * Implements hook_install().
  */
+function image_field_caption_install(): void {
+  _image_field_caption_field_type_schema_column_add_helper('image', ['caption', 'caption_format']);
+}
 
 /**
- * Implements hook_schema().
+ * Implements hook_uninstall().
  */
-function image_field_caption_schema() {
-  // Image Field Caption table.
-  $schema['image_field_caption'] = [
-    'description' => 'The base table for the image_field_caption module.',
-    'fields' => [
-      'entity_type' => [
-        'description' => 'The entity type attached to this caption',
-        'type' => 'varchar',
-        'length' => 128,
-        'not null' => TRUE,
-        'default' => '',
-      ],
-      'bundle' => [
-        'description' => 'The bundle attached to this caption',
-        'type' => 'varchar',
-        'length' => 128,
-        'not null' => TRUE,
-        'default' => '',
-      ],
-      'field_name' => [
-        'description' => 'The field name attached to this caption',
-        'type' => 'varchar',
-        'length' => 32,
-        'not null' => TRUE,
-        'default' => '',
-      ],
-      'entity_id' => [
-        'description' => 'The entity id attached to this caption',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ],
-      'revision_id' => [
-        'description' => 'The entity id attached to this caption, or NULL if the entity type is not versioned',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => FALSE,
-      ],
-      'language' => [
-        'description' => 'The language attached to this caption',
-        'type' => 'varchar',
-        'length' => 32,
-        'not null' => TRUE,
-        'default' => '',
-      ],
-      'delta' => [
-        'description' => 'The sequence number for this caption, used for multi-value fields',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ],
-      'caption' => [
-        'description' => 'The caption text.',
-        'type' => 'text',
-        'not null' => FALSE,
-      ],
-      'caption_format' => [
-        'description' => 'The caption format.',
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => FALSE,
-      ],
-    ],
-    'indexes' => [
-      'entity_type' => ['entity_type'],
-      'bundle' => ['bundle'],
-      'entity_id' => ['entity_id'],
-      'revision_id' => ['revision_id'],
-      'language' => ['language'],
-    ],
-    'primary key' => [
-      'entity_type',
-      'field_name',
-      'entity_id',
-      'language',
-      'delta',
-    ],
-  ];
-
-  // Image Field Caption revision table.
-  $schema['image_field_caption_revision'] = [
-    'description' => 'The revision table for the image_field_caption module.',
-    'fields' => [
-      'entity_type' => [
-        'description' => 'The entity type attached to this caption',
-        'type' => 'varchar',
-        'length' => 128,
-        'not null' => TRUE,
-        'default' => '',
-      ],
-      'bundle' => [
-        'description' => 'The bundle attached to this caption',
-        'type' => 'varchar',
-        'length' => 128,
-        'not null' => TRUE,
-        'default' => '',
-      ],
-      'field_name' => [
-        'description' => 'The field name attached to this caption',
-        'type' => 'varchar',
-        'length' => 32,
-        'not null' => TRUE,
-        'default' => '',
-      ],
-      'entity_id' => [
-        'description' => 'The entity id attached to this caption',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ],
-      'revision_id' => [
-        'description' => 'The entity id attached to this caption, or NULL if the entity type is not versioned',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ],
-      'language' => [
-        'description' => 'The language attached to this caption',
-        'type' => 'varchar',
-        'length' => 32,
-        'not null' => TRUE,
-        'default' => '',
-      ],
-      'delta' => [
-        'description' => 'The sequence number for this caption, used for multi-value fields',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ],
-      'caption' => [
-        'description' => 'The caption text.',
-        'type' => 'text',
-        'not null' => FALSE,
-      ],
-      'caption_format' => [
-        'description' => 'The caption format.',
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => FALSE,
-      ],
-    ],
-    'indexes' => [
-      'entity_type' => ['entity_type'],
-      'bundle' => ['bundle'],
-      'entity_id' => ['entity_id'],
-      'revision_id' => ['revision_id'],
-      'language' => ['language'],
-    ],
-    'primary key' => [
-      'entity_type',
-      'field_name',
-      'entity_id',
-      'revision_id',
-      'language',
-      'delta',
-    ],
-  ];
-
-  return $schema;
+function image_field_caption_uninstall(): void {
+  _image_field_caption_field_type_schema_column_remove_helper('image', ['caption', 'caption_format']);
 }
 
-/* @todo Programmatically set the default formatter for all fields that uses this field formatter using image_field_caption_uninstall(). */
+/**
+ * Helper function to add new columns to a field type.
+ *
+ * @param $field_type
+ *   The field type id.
+ * @param array $columns_to_add
+ *   array of the column names from schema() function.
+ *
+ * @see https://gist.github.com/JPustkuchen/ce53d40303a51ca5f17ce7f48c363b9b
+ * @see https://www.drupal.org/project/drupal/issues/937442
+ */
+function _image_field_caption_field_type_schema_column_add_helper(string $field_type, array $columns_to_add = []): void {
+  $processed_fields = [];
+  $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
+  $field_definition = $field_type_manager->getDefinition($field_type);
+  $field_item_class = $field_definition['class'];
+
+  $schema = \Drupal::database()->schema();
+  $entity_type_manager = \Drupal::entityTypeManager();
+  $entity_field_manager = \Drupal::service('entity_field.manager');
+  $entity_field_map = $entity_field_manager->getFieldMapByFieldType($field_type);
+  // The key-value collection for tracking installed storage schema.
+  $entity_storage_schema_sql = \Drupal::keyValue('entity.storage_schema.sql');
+  $entity_definitions_installed = \Drupal::keyValue('entity.definitions.installed');
+
+  foreach ($entity_field_map as $entity_type_id => $field_map) {
+    $entity_storage = $entity_type_manager->getStorage($entity_type_id);
+
+    // Only SQL storage based entities are supported / throw known exception.
+    if (!($entity_storage instanceof SqlContentEntityStorage)) {
+      continue;
+    }
+
+    $entity_type = $entity_type_manager->getDefinition($entity_type_id);
+    $field_storage_definitions = $entity_field_manager->getFieldStorageDefinitions($entity_type_id);
+    /** @var Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
+    $table_mapping = $entity_storage->getTableMapping($field_storage_definitions);
+    // Only need field storage definitions of our field type:
+    /** @var \Drupal\Core\Field\FieldStorageDefinitionInterface $field_storage_definition */
+    foreach (array_intersect_key($field_storage_definitions, $field_map) as $field_storage_definition) {
+      $field_name = $field_storage_definition->getName();
+      try {
+        $table = $table_mapping->getFieldTableName($field_name);
+      } catch (SqlContentEntityStorageException $e) {
+        // Custom storage? Broken site? No matter what, if there is no table
+        // or column, there's little we can do.
+        continue;
+      }
+      // See if the field has a revision table.
+      $revision_table = NULL;
+      if ($entity_type->isRevisionable() && $field_storage_definition->isRevisionable()) {
+        if ($table_mapping->requiresDedicatedTableStorage($field_storage_definition)) {
+          $revision_table = $table_mapping->getDedicatedRevisionTableName($field_storage_definition);
+        }
+        elseif ($table_mapping->allowsSharedTableStorage($field_storage_definition)) {
+          $revision_table = $entity_type->getRevisionDataTable() ?: $entity_type->getRevisionTable();
+        }
+      }
+      // Load the installed field schema so that it can be updated.
+      $schema_key = "$entity_type_id.field_schema_data.$field_name";
+      $field_schema_data = $entity_storage_schema_sql->get($schema_key);
+
+      $processed_fields[] = [$entity_type_id, $field_name];
+      // Loop over each new column and add it as a schema column change.
+      foreach ($columns_to_add as $column_id) {
+        $column = $table_mapping->getFieldColumnName($field_storage_definition, $column_id);
+        // Add `initial_from_field` to the new spec, as this will copy over
+        // the entire data.
+        $field_schema = $field_item_class::schema($field_storage_definition);
+        $spec = $field_schema['columns'][$column_id];
+
+        // Add the new column.
+        $schema->addField($table, $column, $spec);
+        if ($revision_table) {
+          $schema->addField($revision_table, $column, $spec);
+        }
+
+        // Add the new column to the installed field schema.
+        if (!empty($field_schema_data)) {
+          $field_schema_data[$table]['fields'][$column] = $field_schema['columns'][$column_id];
+          $field_schema_data[$table]['fields'][$column]['not null'] = FALSE;
+          if ($revision_table) {
+            $field_schema_data[$revision_table]['fields'][$column] = $field_schema['columns'][$column_id];
+            $field_schema_data[$revision_table]['fields'][$column]['not null'] = FALSE;
+          }
+        }
+      }
+
+      // Save changes to the installed field schema.
+      if (!empty($field_schema_data)) {
+        $entity_storage_schema_sql->set($schema_key, $field_schema_data);
+      }
+      if ($table_mapping->allowsSharedTableStorage($field_storage_definition)) {
+        $key = "$entity_type_id.field_storage_definitions";
+        if ($definitions = $entity_definitions_installed->get($key)) {
+          $definitions[$field_name] = $field_storage_definition;
+          $entity_definitions_installed->set($key, $definitions);
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Helper function to remove columns from a field type.
+ *
+ * @param $field_type
+ *   The field type id.
+ * @param array $columns_to_remove
+ *   array of the column names from schema() function.
+ *
+ * @see https://gist.github.com/JPustkuchen/ce53d40303a51ca5f17ce7f48c363b9b
+ * @see https://www.drupal.org/project/drupal/issues/937442
+ */
+function _image_field_caption_field_type_schema_column_remove_helper(string $field_type, array $columns_to_remove = []): void {
+  $processed_fields = [];
+  $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
+  $field_definition = $field_type_manager->getDefinition($field_type);
+  $field_item_class = $field_definition['class'];
+
+  $schema = \Drupal::database()->schema();
+  $entity_type_manager = \Drupal::entityTypeManager();
+  $entity_field_manager = \Drupal::service('entity_field.manager');
+  $entity_field_map = $entity_field_manager->getFieldMapByFieldType($field_type);
+  // The key-value collection for tracking installed storage schema.
+  $entity_storage_schema_sql = \Drupal::keyValue('entity.storage_schema.sql');
+  $entity_definitions_installed = \Drupal::keyValue('entity.definitions.installed');
+
+  foreach ($entity_field_map as $entity_type_id => $field_map) {
+    $entity_storage = $entity_type_manager->getStorage($entity_type_id);
+
+    // Only SQL storage based entities are supported / throw known exception.
+    if (!($entity_storage instanceof SqlContentEntityStorage)) {
+      continue;
+    }
+
+    $entity_type = $entity_type_manager->getDefinition($entity_type_id);
+    $field_storage_definitions = $entity_field_manager->getFieldStorageDefinitions($entity_type_id);
+    /** @var Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
+    $table_mapping = $entity_storage->getTableMapping($field_storage_definitions);
+    // Only need field storage definitions of our field type:
+    /** @var \Drupal\Core\Field\FieldStorageDefinitionInterface $field_storage_definition */
+    foreach (array_intersect_key($field_storage_definitions, $field_map) as $field_storage_definition) {
+      $field_name = $field_storage_definition->getName();
+      try {
+        $table = $table_mapping->getFieldTableName($field_name);
+      } catch (SqlContentEntityStorageException $e) {
+        // Custom storage? Broken site? No matter what, if there is no table
+        // or column, there's little we can do.
+        continue;
+      }
+      // See if the field has a revision table.
+      $revision_table = NULL;
+      if ($entity_type->isRevisionable() && $field_storage_definition->isRevisionable()) {
+        if ($table_mapping->requiresDedicatedTableStorage($field_storage_definition)) {
+          $revision_table = $table_mapping->getDedicatedRevisionTableName($field_storage_definition);
+        }
+        elseif ($table_mapping->allowsSharedTableStorage($field_storage_definition)) {
+          $revision_table = $entity_type->getRevisionDataTable() ?: $entity_type->getRevisionTable();
+        }
+      }
+      // Load the installed field schema so that it can be updated.
+      $schema_key = "$entity_type_id.field_schema_data.$field_name";
+      $field_schema_data = $entity_storage_schema_sql->get($schema_key);
+
+      $processed_fields[] = [$entity_type_id, $field_name];
+      // Loop over each new column and add it as a schema column change.
+      foreach ($columns_to_remove as $column_id) {
+        $column = $table_mapping->getFieldColumnName($field_storage_definition, $column_id);
+        // Add `initial_from_field` to the new spec, as this will copy over
+        // the entire data.
+        $field_schema = $field_item_class::schema($field_storage_definition);
+        $spec = $field_schema['columns'][$column_id];
+
+        // Add the new column.
+        $schema->dropField($table, $column);
+        if ($revision_table) {
+          $schema->dropField($revision_table, $column);
+        }
+
+        // Remove the column from the installed field schema.
+        if (!empty($field_schema_data)) {
+          unset($field_schema_data[$table]['fields'][$column]);
+          if ($revision_table) {
+            unset($field_schema_data[$revision_table]['fields'][$column]);
+          }
+        }
+      }
+
+      // Save changes to the installed field schema.
+      if (!empty($field_schema_data)) {
+        $entity_storage_schema_sql->set($schema_key, $field_schema_data);
+      }
+      if ($table_mapping->allowsSharedTableStorage($field_storage_definition)) {
+        $key = "$entity_type_id.field_storage_definitions";
+        if ($definitions = $entity_definitions_installed->get($key)) {
+          $definitions[$field_name] = $field_storage_definition;
+          $entity_definitions_installed->set($key, $definitions);
+        }
+      }
+    }
+  }
+}
diff --git a/image_field_caption.module b/image_field_caption.module
index d49aa67..63f8999 100644
--- a/image_field_caption.module
+++ b/image_field_caption.module
@@ -5,112 +5,72 @@
  * Provides a caption textarea for image fields.
  */
 
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\FieldableEntityInterface;
 use Drupal\Core\Field\FieldDefinitionInterface;
 use Drupal\Core\Form\FormStateInterface;
-use Drupal\Component\Utility\NestedArray;
-
-/**
- * List to do.
- *
- * Support for Views, maybe built in in D8?
- * Support the revision management.
- * */
+use Drupal\image\Plugin\Field\FieldWidget\ImageWidget;
+use Drupal\image_field_caption\ImageCaptionItem;
 
 /**
  * Implements hook_field_info_alter().
  */
-function image_field_caption_field_info_alter(&$info) {
-  // Set a new class for the image fields.
-  $info['image']['class'] = '\Drupal\image_field_caption\ImageCaptionItem';
+function image_field_caption_field_info_alter(array &$info): void {
+  // Override the image field type class
+  $info['image']['class'] = ImageCaptionItem::class;
+
+  // Enable translation for the caption property
+  $info['image']['column_groups']['caption'] = [
+    'label' => t('Caption'),
+    'translatable' => TRUE,
+  ];
 }
 
 /**
  * Implements hook_field_widget_single_element_form_alter().
  */
-function image_field_caption_field_widget_single_element_form_alter(&$element, FormStateInterface $form_state, $context) {
-  /** @var \Drupal\field\Entity\FieldConfig $field */
+function image_field_caption_field_widget_single_element_form_alter(array &$element, FormStateInterface $form_state, $context): void {
   $field = $context['items']->getFieldDefinition();
-  // If the current field is an image field.
-  if ($field->getType() == 'image') {
-    // Get the current field settings.
-    $settings = $field->getSettings();
-    // Check if the current field has the caption.
-    if (!empty($settings['caption_field'])) {
-      $element['#caption_field_required'] = $settings['caption_field_required'];
-      $element['#process'][] = '_image_field_caption_widget_process';
-    }
+  assert($field instanceof FieldDefinitionInterface);
+
+  if ($field->getType() !== 'image') {
+    return;
   }
+
+  $settings = $field->getSettings();
+  $element['#caption_field'] = $settings['caption_field'];
+  $element['#caption_field_required'] = $settings['caption_field_required'];
+  $element['#process'][] = '_image_field_caption_widget_process';
 }
 
 /**
  * Custom callback function for the #process of an image field type.
  */
-function _image_field_caption_widget_process($element, &$form_state, $form) {
-  // Get the entity.
-  // $entity = $form_state->getFormObject()->getEntity();
-  // // Get the fields definitions.
-  // $field_definitions = $entity->getFieldDefinitions();
-  // // Get the current field definition.
-  // if (!empty($field_definitions[$element['#field_name']])) {
-  // $field_definition = $field_definitions[$element['#field_name']];
-  // }
-  // elseif (!empty($field_definitions[$element['#field_parents'][0]])) {
-  // $field_definition = $field_definitions[$element['#field_parents'][0]];
-  // }
-  // else {
-  // $field_definition = NULL;
-  // }.
-  // Get the current field values (form state).
-  $field_values = $form_state->getValues();
-  // If the field has parents (ex: paragraphs) then get the nested values.
-  if (!empty($element['#field_parents'])) {
-    $field_values = NestedArray::getValue($field_values, $element['#field_parents']);
-  }
-  $field_value = (isset($field_values[$element['#field_name']][$element['#delta']]['image_field_caption'])) ? $field_values[$element['#field_name']][$element['#delta']]['image_field_caption'] : [];
+function _image_field_caption_widget_process(array $element, FormStateInterface $form_state, array $form): array {
+  $item = $element['#value'];
+  $item['fids'] = $element['fids']['#value'];
 
-  // Add the additional caption fields.
-  $element['image_field_caption'] = [
+  // TODO: Make #allowed_formats configurable.
+
+  $element['caption'] = [
     '#title' => t('Caption'),
     '#type' => 'text_format',
-    '#value' => (!empty($field_value['value'])) ? $field_value['value'] : ((!empty($element['#value']['caption'])) ? $element['#value']['caption'] : []),
-    '#default_value' => (!empty($element['#value']['caption'])) ? $element['#value']['caption'] : (!empty($element['#value']['image_field_caption']) ? $element['#value']['image_field_caption']['value'] : ''),
-    '#access' => (bool) $element['#value']['fids'],
-    '#format' => (!empty($field_value['format'])) ? $field_value['format'] : ((!empty($element['#value']['caption_format'])) ? $element['#value']['caption_format'] : 'plain_text'),
+    '#default_value' => $item['caption'] ?? '',
+    '#access' => $item['fids'] && $element['#caption_field'],
+    '#format' => $item['caption_format'] ?? 'plain_text',
     '#required' => $element['#caption_field_required'],
-    '#element_validate' => $element['#caption_field_required'] ? ['_image_field_caption_validate_required'] : [],
+    '#element_validate' => $element['#caption_field_required'] ? [[ImageWidget::class, 'validateRequiredFields']] : [],
   ];
 
   return $element;
 }
 
-/**
- * Validate callback for caption field, if the user wants them required.
- *
- * This is separated in a validate function instead of a #required flag to
- * avoid being validated on the process callback.
- */
-function _image_field_caption_validate_required($element, FormStateInterface $form_state) {
-  // Only do validation if the function is triggered from other places than
-  // the image process form.
-  // Only do validation if the function is triggered from other places than
-  // the image process form.
-  $triggering_element = $form_state->getTriggeringElement();
-  if (!empty($triggering_element['#submit']) && in_array('file_managed_file_submit', $triggering_element['#submit'], TRUE)) {
-    $form_state->setLimitValidationErrors([]);
-  }
-}
-
 /**
  * Implements hook_theme().
  */
-function image_field_caption_theme() {
+function image_field_caption_theme(): array {
   return [
     'image_caption_formatter' => [
       // As we extend the default image format, the variables passed to the
-      // callback function are the same than the original
-      // "callback" function ("image_formatter").
+      // callback function are the same as the original (image_formatter).
       'variables' => [
         'item' => NULL,
         'item_attributes' => NULL,
@@ -131,10 +91,11 @@ function image_field_caption_theme() {
  *   (template_preprocess_image_formatter()) and also:
  *   - caption: An optional caption text.
  */
-function template_preprocess_image_caption_formatter(&$variables) {
-  Drupal::moduleHandler()->loadInclude('image', 'inc', 'image.field');
+function template_preprocess_image_caption_formatter(array &$variables): void {
   // Prepare the variables array with the original function.
+  Drupal::moduleHandler()->loadInclude('image', 'inc', 'image.field');
   template_preprocess_image_formatter($variables);
+
   // Set the caption value.
   $values = $variables['item']->getValue();
   if (!empty($values['caption'])) {
@@ -147,187 +108,19 @@ function template_preprocess_image_caption_formatter(&$variables) {
 }
 
 /**
- * Implements hook_entity_storage_load().
- */
-function image_field_caption_entity_storage_load(array $entities, $entity_type_id) {
-  $imageCaption = Drupal::service('image_field_caption.storage');
-
-  if (in_array($entity_type_id, $imageCaption->list('entity_type'))) {
-    // This means we already have some captions.
-    // No need to do all kinds of checking then.
-    /** @var \Drupal\Core\Entity\Entity $entity */
-    foreach ($entities as $entity) {
-      // Same load avoiding check.
-      if (in_array($entity->bundle(), $imageCaption->list('bundle'))) {
-        $needToSave = FALSE;
-
-        /** @var \Drupal\Core\Field\FieldItemList $field */
-        foreach ($entity->getFields() as $fieldName => $field) {
-          $values = $entity->get($fieldName)->getValue();
-          foreach ($values as $delta => $value) {
-            // Get the caption associated to this field.
-            $revision_id = (empty($entity->getRevisionId()) ? $entity->id() : $entity->getRevisionId());
-            $caption = $imageCaption->getCaption(
-              $entity->getEntityTypeId(),
-              $entity->bundle(),
-              $fieldName,
-              $entity->id(),
-              $revision_id,
-              $entity->language()->getId(),
-              $delta
-            );
-
-            // Set the caption value.
-            if (!empty($caption)) {
-              $values[$delta] = $values[$delta] + $caption;
-              $needToSave = TRUE;
-            }
-          }
-
-          if ($needToSave) {
-            // Save all values.
-            $entity->get($fieldName)->setValue($values);
-          }
-        }
-
-      }
-    }
-  }
-}
-
-/**
- * Implements hook_entity_insert().
+ * Implements hook_config_schema_info_alter().
  */
-function image_field_caption_entity_insert(EntityInterface $entity) {
-  image_field_caption_entity_update($entity);
-}
-
-/**
- * Implements hook_entity_update().
- */
-function image_field_caption_entity_update(EntityInterface $entity) {
-  $imageCaption = Drupal::service('image_field_caption.storage');
-
-  // For a fieldable entity.
-  if (($entity instanceof FieldableEntityInterface)) {
-    // Get the field names of all image fields.
-    $field_names = _image_field_caption_get_image_field_names($entity);
-    foreach ($field_names as $field_name) {
-      // Get the current field settings.
-      $settings = $entity->get($field_name)->getSettings();
-      // If the caption is not enabled => pass this field.
-      if (empty($settings['caption_field'])) {
-        continue;
-      }
-      // Delete the caption associated to this field.
-      $imageCaption->deleteCaption($entity->getEntityTypeId(), $entity->bundle(), $field_name, $entity->id(), $entity->language()
-        ->getId());
-      // Delete the caption revision associated to this field.
-      /*
-      $imageCaption->deleteCaptionRevision(
-      $entity->getEntityTypeId(), $entity->bundle(),
-      $field_name, $entity->id(), $entity->getRevisionId(),
-      $entity->language()->getId()); */
-      // Get the current field values.
-      $values = $entity->get($field_name)->getValue();
-      foreach ($values as $delta => $value) {
-        // If a caption text is defined.
-        if (!empty($value['image_field_caption']['value'])) {
-          // Insert the caption associated to this field.
-          // @todo Do the insertion using a multiple query instead several queries into a foreach;
-          $revision_id = (empty($entity->getRevisionId()) ? $entity->id() : $entity->getRevisionId());
-          $imageCaption->insertCaption(
-            $entity->getEntityTypeId(),
-            $entity->bundle(),
-            $field_name,
-            $entity->id(),
-            $revision_id,
-            $entity->language()->getId(),
-            $delta,
-            $value['image_field_caption']['value'],
-            $value['image_field_caption']['format']
-          );
-          // Insert the caption revision associated to this field.
-          /*
-          if ($entity->isNewRevision()) {
-          $imageCaption->insertCaptionRevision(
-          $entity->getEntityTypeId(),
-          $entity->bundle(),
-          $field_name,
-          $entity->id(),
-          $revision_id,
-          $entity->language()->getId(),
-          $delta,
-          $value['image_field_caption']['value'],
-          $value['image_field_caption']['format']
-          );
-          }
-           */
-        }
-      }
-    }
-  }
-}
-
-/**
- * Implements hook_entity_delete().
- */
-function image_field_caption_entity_delete(EntityInterface $entity) {
-  $imageCaption = Drupal::service('image_field_caption.storage');
-
-  // For a fieldable entity.
-  if (($entity instanceof FieldableEntityInterface)) {
-    // Get the field names of all image fields.
-    $field_names = _image_field_caption_get_image_field_names($entity);
-    foreach ($field_names as $field_name) {
-      // Delete the caption associated to this field.
-      $imageCaption->deleteCaption($entity->getEntityTypeId(), $entity->bundle(), $field_name, $entity->id(), $entity->language()
-        ->getId());
-      // Delete the caption revisions associated to this field.
-      /*
-      $imageCaption->deleteCaptionRevisions(
-      $entity->getEntityTypeId(), $entity->bundle(),
-      $field_name, $entity->id(), $entity->language()->getId()
-      );*/
-    }
-  }
-}
-
-/**
- * Implements hook_entity_revision_delete().
- */
-function image_field_caption_entity_revision_delete(EntityInterface $entity) {
-  // $imageCaption = Drupal::service('image_field_caption.storage');
-  /*
-  // For a fieldable entity.
-  if (($entity instanceof FieldableEntityInterface)) {
-  // Get the field names of all image fields.
-  $field_names = _image_field_caption_get_image_field_names($entity);
-  if (!empty($field_names)) {
-  // Delete the caption revisions associated to this specific revision.
-  $imageCaption->deleteCaptionRevisionsByRevisionId($entity->getRevisionId());
-  }
-  }
-   */
-}
-
-/**
- * Determines the image fields on an entity.
- *
- * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
- *   An entity whose fields to analyze.
- *
- * @return array
- *   The names of the fields on this entity that support formatted text.
- */
-function _image_field_caption_get_image_field_names(FieldableEntityInterface $entity) {
-  // Check if fields definitions are available.
-  $field_definitions = $entity->getFieldDefinitions();
-  if (empty($field_definitions)) {
-    return [];
-  }
-  // Only return image fields.
-  return array_keys(array_filter($field_definitions, function (FieldDefinitionInterface $definition) {
-    return in_array($definition->getType(), ['image'], TRUE);
-  }));
+function image_field_caption_config_schema_info_alter(array &$definitions): void {
+  $definitions['field.field_settings.image']['mapping']['caption_field'] = [
+    'type' => 'boolean',
+    'label' => 'Enable Caption field',
+  ];
+  $definitions['field.field_settings.image']['mapping']['caption_field_required'] = [
+    'type' => 'boolean',
+    'label' => 'Caption field required',
+  ];
+  $definitions['field_default_image']['mapping']['caption'] = [
+    'type' => 'label',
+    'label' => 'Caption',
+  ];
 }
diff --git a/image_field_caption.services.yml b/image_field_caption.services.yml
deleted file mode 100644
index 845c2e6..0000000
--- a/image_field_caption.services.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-services:
-  image_field_caption.storage:
-    class: Drupal\image_field_caption\ImageCaptionStorage
-    arguments:
-      - '@cache.data'
-      - '@cache_tags.invalidator'
-      - '@database'
diff --git a/src/ImageCaptionItem.php b/src/ImageCaptionItem.php
index 1367735..02c0ada 100644
--- a/src/ImageCaptionItem.php
+++ b/src/ImageCaptionItem.php
@@ -2,46 +2,90 @@
 
 namespace Drupal\image_field_caption;
 
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\Core\TypedData\DataDefinition;
 use Drupal\image\Plugin\Field\FieldType\ImageItem;
 
 /**
- * Class to provide functionlality for ImageCaptionItem.
+ * Class to provide functionality for ImageCaptionItem.
  */
 class ImageCaptionItem extends ImageItem {
 
+  /**
+   * {@inheritdoc}
+   */
+  public static function defaultStorageSettings() {
+    $settings = parent::defaultStorageSettings();
+    $settings['default_image']['caption'] = '';
+
+    return $settings;
+  }
+
   /**
    * {@inheritdoc}
    */
   public static function defaultFieldSettings() {
-    return [
-      'default_image' => [
-        'caption' => '',
-      ],
-      'caption_field' => FALSE,
-      'caption_field_required' => FALSE,
-    ] + parent::defaultFieldSettings();
+    $settings = parent::defaultFieldSettings();
+    $settings['default_image']['caption'] = '';
+    $settings['caption_field'] = FALSE;
+    $settings['caption_field_required'] = FALSE;
+
+    return $settings;
+  }
+
+  public static function schema(FieldStorageDefinitionInterface $field_definition) {
+    $schema = parent::schema($field_definition);
+
+    $schema['columns']['caption'] = [
+      'description' => 'The caption text.',
+      'type' => 'text',
+      'not null' => FALSE,
+    ];
+
+    $schema['columns']['caption_format'] = [
+      'description' => 'The caption format.',
+      'type' => 'varchar',
+      'length' => 255,
+      'not null' => FALSE,
+    ];
+
+    return $schema;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
+    $properties = parent::propertyDefinitions($field_definition);
+
+    $properties['caption'] = DataDefinition::create('string')
+      ->setLabel(new TranslatableMarkup('Caption'))
+      ->setDescription(new TranslatableMarkup("Short description of the image displayed underneath the image."));
+
+    $properties['caption_format'] = DataDefinition::create('filter_format')
+        ->setLabel(new TranslatableMarkup('Text format of the caption'));
+
+    return $properties;
   }
 
   /**
    * {@inheritdoc}
    */
   public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
-    // Get base form from ImageItem.
     $element = parent::fieldSettingsForm($form, $form_state);
-    // Get field settings.
     $settings = $this->getSettings();
-    // Get the default field settings.
-    $settings_default = self::defaultFieldSettings();
 
     // Add caption option.
     $element['caption_field'] = [
       '#type' => 'checkbox',
       '#title' => t('Enable <em>Caption</em> field'),
-      '#default_value' => (!empty($settings['caption_field'])) ? $settings['caption_field'] : $settings_default['caption_field'],
-      '#description' => t('Adds an extra text area for captions on image fields.'),
+      '#default_value' => $settings['caption_field'],
+      '#description' => $this->t('Short description of the image displayed underneath the image.'),
       '#weight' => 13,
     ];
+
     // Add caption (required) option.
     $element['caption_field_required'] = [
       '#type' => 'checkbox',
@@ -51,16 +95,44 @@ class ImageCaptionItem extends ImageItem {
       '#weight' => 14,
       '#states' => [
         'visible' => [
-          ':input[name="settings[image_caption_field]"]' => ['checked' => TRUE],
+          ':input[name="settings[caption_field]"]' => ['checked' => TRUE],
         ],
       ],
     ];
+
     // Add default caption.
     $element['default_image']['caption'] = [
       '#type' => 'value',
-      '#value' => (!empty($settings['default_image']['caption'])) ? $settings['default_image']['caption'] : $settings_default['default_image']['caption'],
+      '#value' => $settings['default_image']['caption'] ?? '',
     ];
 
     return $element;
   }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function defaultImageForm(array &$element, array $settings) {
+    parent::defaultImageForm($element, $settings);
+
+    $element['default_image']['caption'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Caption'),
+      '#description' => $this->t('Short description of the image displayed underneath the image.'),
+      '#default_value' => $settings['default_image']['caption'] ?? '',
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setValue($values, $notify = TRUE): void {
+    if (isset($values['caption']) && is_array($values['caption'])) {
+      $values['caption_format'] = $values['caption']['format'];
+      $values['caption'] = $values['caption']['value'];
+    }
+
+    parent::setValue($values, $notify);
+  }
+
 }
diff --git a/src/ImageCaptionStorage.php b/src/ImageCaptionStorage.php
deleted file mode 100644
index 9640903..0000000
--- a/src/ImageCaptionStorage.php
+++ /dev/null
@@ -1,425 +0,0 @@
-<?php
-
-namespace Drupal\image_field_caption;
-
-use Drupal\Core\Database\Connection;
-use Drupal\Core\Cache\CacheBackendInterface;
-use Drupal\Core\Cache\Cache;
-use Drupal\Core\Cache\CacheTagsInvalidator;
-
-/**
- * Storage controller class for image captions.
- *
- * @todo Use array with key/value as argument instead several arguments.
- * @todo The methods isCaption() and deleteCaption() must manage the revisions by itself, instead to have two differents methods.
- */
-class ImageCaptionStorage {
-
-  /**
-   * The Cache Backend.
-   *
-   * @var \Drupal\Core\Cache\CacheBackendInterface
-   */
-  protected $cacheBackend;
-
-  /**
-   * The Cache Tags Invalidator.
-   *
-   * @var \Drupal\Core\Cache\CacheTagsInvalidator
-   */
-  protected $cacheTagsInvalidator;
-
-  /**
-   * The database connection.
-   *
-   * @var \Drupal\Core\Database\Connection
-   */
-  protected $database;
-
-  /**
-   * The name of the data table.
-   *
-   * @var string
-   */
-  protected $tableData = 'image_field_caption';
-
-  /**
-   * The name of the revision table.
-   *
-   * @var string
-   */
-  protected $tableRevision = 'image_field_caption_revision';
-
-  /**
-   * AbstractService constructor.
-   *
-   * @param \Drupal\Core\Cache\CacheBackendInterface $cacheBackend
-   *   The Cache Backend.
-   * @param \Drupal\Core\Cache\CacheTagsInvalidatorInterface $cacheTagsInvalidator
-   *   The cache tags invalidator.
-   * @param \Drupal\Core\Database\Connection $database
-   *   The Database.
-   */
-  public function __construct(
-        CacheBackendInterface $cacheBackend,
-        CacheTagsInvalidator $cacheTagsInvalidator,
-        Connection $database
-    ) {
-    $this->cacheBackend = $cacheBackend;
-    $this->cacheTagsInvalidator = $cacheTagsInvalidator;
-    $this->database = $database;
-  }
-
-  /**
-   * Check if a caption is already defined for the specified arguments.
-   *
-   * @param string $entity_type
-   *   The entity type, like 'node' or 'comment'.
-   * @param string $bundle
-   *   The bundle, like 'article' or 'news'.
-   * @param string $field_name
-   *   The field name of the image field,
-   *   like 'field_image' or 'field_article_image'.
-   * @param int $entity_id
-   *   The entity id.
-   * @param int $revision_id
-   *   The revision id.
-   * @param string $language
-   *   The language key, like 'en' or 'fr'.
-   * @param int $delta
-   *   The delta of the image field.
-   *
-   * @return bool
-   *   TRUE if a caption exists or FALSE if not.
-   */
-  public function isCaption($entity_type, $bundle, $field_name, $entity_id, $revision_id, $language, $delta) {
-    return (!empty(self::getCaption($entity_type, $bundle, $field_name, $entity_id, $revision_id, $language, $delta))) ? TRUE : FALSE;
-  }
-
-  /**
-   * Get a caption from the database for the specified arguments.
-   *
-   * @param string $entity_type
-   *   The entity type, like 'node' or 'comment'.
-   * @param string $bundle
-   *   The bundle, like 'article' or 'news'.
-   * @param string $field_name
-   *   The field name of the image field,
-   *   like 'field_image' or 'field_article_image'.
-   * @param int $entity_id
-   *   The entity id.
-   * @param int $revision_id
-   *   The revision id.
-   * @param string $language
-   *   The language key, like 'en' or 'fr'.
-   * @param int $delta
-   *   The delta of the image field.
-   *
-   * @return array
-   *   A caption array
-   *   - caption: The caption text.
-   *   - caption_format: The caption format.
-   *   or an empty array, if no value found.
-   */
-  public function getCaption($entity_type, $bundle, $field_name, $entity_id, $revision_id, $language, $delta) {
-    $captions = &drupal_static(__FUNCTION__);
-
-    $cacheKey = $this->getCacheKey($entity_type, $entity_id, $revision_id, $language, $field_name, $delta);
-
-    if (isset($captions[$cacheKey])) {
-      $caption = $captions[$cacheKey];
-    }
-    elseif ($cached = $this->cacheBackend->get($cacheKey)) {
-      $caption = $cached->data;
-    }
-    else {
-      // Query.
-      $query = $this->database->select($this->tableData, 'ifc');
-      $result = $query
-        ->fields('ifc', ['caption', 'caption_format'])
-        ->condition('entity_type', $entity_type, '=')
-        ->condition('bundle', $bundle, '=')
-        ->condition('field_name', $field_name, '=')
-        ->condition('entity_id', $entity_id, '=')
-        ->condition('revision_id', $revision_id, '=')
-        ->condition('language', $language, '=')
-        ->condition('delta', $delta, '=')
-        ->execute()
-        ->fetchAssoc();
-
-      // Caption array.
-      $caption = [];
-      if (!empty($result)) {
-        $caption = $result;
-      }
-
-      // Let the cache depends on the entity.
-      // @todo Use getCacheTags() to get the default list.
-      $this->cacheBackend->set(
-            $cacheKey,
-            $caption,
-            Cache::PERMANENT,
-            [
-              $field_name,
-              'image_field_caption',
-            ]
-        );
-    }
-
-    return $caption;
-  }
-
-  /**
-   * Insert a caption into the database for the specified arguments.
-   *
-   * @param string $entity_type
-   *   The entity type, like 'node' or 'comment'.
-   * @param string $bundle
-   *   The bundle, like 'article' or 'news'.
-   * @param string $field_name
-   *   The field name of the image field,
-   *   like 'field_image' or 'field_article_image'.
-   * @param int $entity_id
-   *   The entity id.
-   * @param int $revision_id
-   *   The revision id.
-   * @param string $language
-   *   The language key, like 'en' or 'fr'.
-   * @param int $delta
-   *   The delta of the image field.
-   * @param string $caption
-   *   The caption text.
-   * @param string $caption_format
-   *   The text format of the caption.
-   */
-  public function insertCaption($entity_type, $bundle, $field_name, $entity_id, $revision_id, $language, $delta, $caption, $caption_format) {
-
-    $query = $this->database->insert($this->tableData);
-    $query
-      ->fields([
-        'entity_type' => $entity_type,
-        'bundle' => $bundle,
-        'field_name' => $field_name,
-        'entity_id' => $entity_id,
-        'revision_id' => $revision_id,
-        'language' => $language,
-        'delta' => $delta,
-        'caption' => $caption,
-        'caption_format' => $caption_format,
-      ])
-      ->execute();
-    $this->clearCache($field_name);
-  }
-
-  /**
-   * Insert a caption revision into the database for the specified arguments.
-   *
-   * @param string $entity_type
-   *   The entity type, like 'node' or 'comment'.
-   * @param string $bundle
-   *   The bundle, like 'article' or 'news'.
-   * @param string $field_name
-   *   The field name of the image field,
-   *   like 'field_image' or 'field_article_image'.
-   * @param int $entity_id
-   *   The entity id.
-   * @param int $revision_id
-   *   The revision id.
-   * @param string $language
-   *   The language key, like 'en' or 'fr'.
-   * @param int $delta
-   *   The delta of the image field.
-   * @param string $caption
-   *   The caption text.
-   * @param string $caption_format
-   *   The text format of the caption.
-   */
-  public function insertCaptionRevision($entity_type, $bundle, $field_name, $entity_id, $revision_id, $language, $delta, $caption, $caption_format) {
-    $query = $this->database->insert($this->tableRevision);
-    $query
-      ->fields([
-        'entity_type' => $entity_type,
-        'bundle' => $bundle,
-        'field_name' => $field_name,
-        'entity_id' => $entity_id,
-        'revision_id' => $revision_id,
-        'language' => $language,
-        'delta' => $delta,
-        'caption' => $caption,
-        'caption_format' => $caption_format,
-      ])
-      ->execute();
-    $this->clearCache($field_name);
-  }
-
-  /**
-   * Delete a caption from the database for the specified arguments.
-   *
-   * @param string $entity_type
-   *   The entity type, like 'node' or 'comment'.
-   * @param string $bundle
-   *   The bundle, like 'article' or 'news'.
-   * @param string $field_name
-   *   The field name of the image field, like
-   *   'field_image' or 'field_article_image'.
-   * @param int $entity_id
-   *   The entity id.
-   * @param string $language
-   *   The language key, like 'en' or 'fr'.
-   */
-  public function deleteCaption($entity_type, $bundle, $field_name, $entity_id, $language) {
-    $query = $this->database->delete($this->tableData);
-    $query
-      ->condition('entity_type', $entity_type, '=')
-      ->condition('bundle', $bundle, '=')
-      ->condition('field_name', $field_name, '=')
-      ->condition('entity_id', $entity_id, '=')
-      ->condition('language', $language, '=')
-      ->execute();
-    $this->clearCache($field_name);
-    // @todo Try to return the count of the affected rows.
-  }
-
-  /**
-   * Delete a caption revision from the database for the specified arguments.
-   *
-   * @param string $entity_type
-   *   The entity type, like 'node' or 'comment'.
-   * @param string $bundle
-   *   The bundle, like 'article' or 'news'.
-   * @param string $field_name
-   *   The field name of the image field, like
-   *   'field_image' or 'field_article_image'.
-   * @param int $entity_id
-   *   The entity id.
-   * @param int $revision_id
-   *   The revision id.
-   * @param string $language
-   *   The language key, like 'en' or 'fr'.
-   */
-  public function deleteCaptionRevision($entity_type, $bundle, $field_name, $entity_id, $revision_id, $language) {
-    $query = $this->database->delete($this->tableRevision);
-    $query
-      ->condition('entity_type', $entity_type, '=')
-      ->condition('bundle', $bundle, '=')
-      ->condition('field_name', $field_name, '=')
-      ->condition('entity_id', $entity_id, '=')
-      ->condition('revision_id', $revision_id, '=')
-      ->condition('language', $language, '=')
-      ->execute();
-    $this->clearCache($field_name);
-    // @todo Try to return the count of the affected rows.
-  }
-
-  /**
-   * Delete all captions revisions for the specified arguments.
-   *
-   * @param string $entity_type
-   *   The entity type, like 'node' or 'comment'.
-   * @param string $bundle
-   *   The bundle, like 'article' or 'news'.
-   * @param string $field_name
-   *   The field name of the image field, like 'field_image'
-   *   or 'field_article_image'.
-   * @param int $entity_id
-   *   The entity id.
-   * @param string $language
-   *   The language key, like 'en' or 'fr'.
-   */
-  public function deleteCaptionRevisions($entity_type, $bundle, $field_name, $entity_id, $language) {
-    $query = $this->database->delete($this->tableRevision);
-    $query
-      ->condition('entity_type', $entity_type, '=')
-      ->condition('bundle', $bundle, '=')
-      ->condition('field_name', $field_name, '=')
-      ->condition('entity_id', $entity_id, '=')
-      ->condition('language', $language, '=')
-      ->execute();
-    $this->clearCache($field_name);
-    // @todo Try to return the count of the affected rows.
-  }
-
-  /**
-   * Delete all captions revisions for a specific revision id.
-   *
-   * @param int $revision_id
-   *   The revision id.
-   */
-  public function deleteCaptionRevisionsByRevisionId($revision_id) {
-    $query = $this->database->delete($this->tableRevision);
-    $query
-      ->condition('revision_id', $revision_id, '=')
-      ->execute();
-  }
-
-  /**
-   * Clears the cache for a certain field name.
-   *
-   * @param string $field_name
-   *   The field name of the image field, like 'field_image'
-   *   or 'field_article_image'.
-   */
-  public function clearCache($field_name) {
-    $this->cacheTagsInvalidator->invalidateTags([
-      $field_name,
-      'image_field_caption',
-    ]);
-  }
-
-  /**
-   * Constructs the cache key.
-   *
-   * @param string $entity_type
-   *   The entity type, like 'node' or 'comment'.
-   * @param int $entity_id
-   *   The entity id.
-   * @param int $revision_id
-   *   The revision id.
-   * @param string $language
-   *   The language key, like 'en' or 'fr'.
-   * @param string $field_name
-   *   The field name of the image field, like 'field_image'
-   *   or 'field_article_image'.
-   * @param int $delta
-   *   The delta of the image field.
-   */
-  public function getCacheKey($entity_type, $entity_id, $revision_id, $language, $field_name, $delta) {
-    return implode(
-        ":",
-        [
-          'caption',
-          $entity_type,
-          $entity_id,
-          $revision_id,
-          $language,
-          $field_name,
-          $delta,
-        ]
-    );
-  }
-
-  /**
-   * Function to list out entity type field.
-   */
-  public function list($key = 'entity_type') {
-    $list = &drupal_static(__FUNCTION__);
-
-    if (!isset($list[$key])) {
-      // Query.
-      $query = $this->database->select($this->tableData, 'ifc');
-      $result = $query
-        ->fields('ifc', [$key])
-        ->distinct()
-        ->execute()
-        ->fetchAll();
-
-      $list[$key] = [];
-      foreach ($result as $row) {
-        $list[$key][] = $row->{$key};
-      }
-    }
-
-    return $list[$key];
-  }
-
-}
diff --git a/src/Plugin/Field/FieldFormatter/ImageCaptionFormatter.php b/src/Plugin/Field/FieldFormatter/ImageCaptionFormatter.php
index 7ba4c62..c761a0d 100644
--- a/src/Plugin/Field/FieldFormatter/ImageCaptionFormatter.php
+++ b/src/Plugin/Field/FieldFormatter/ImageCaptionFormatter.php
@@ -23,10 +23,12 @@ class ImageCaptionFormatter extends ImageFormatter {
    */
   public function viewElements(FieldItemListInterface $items, $langcode) {
     $elements = parent::viewElements($items, $langcode);
+
     foreach ($elements as $delta => $element) {
       // Set a new theme callback function for the image caption formatter.
       $elements[$delta]['#theme'] = 'image_caption_formatter';
     }
+
     return $elements;
   }
 
-- 
GitLab


From 0066f4a953597385e284bfb5bdee941c4dc78b66 Mon Sep 17 00:00:00 2001
From: Dieter Holvoet <dieter.holvoet@gmail.com>
Date: Mon, 9 Sep 2024 11:28:27 +0200
Subject: [PATCH 2/4] Add back image module dependency

---
 image_field_caption.info.yml | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/image_field_caption.info.yml b/image_field_caption.info.yml
index e04a8e9..4861584 100644
--- a/image_field_caption.info.yml
+++ b/image_field_caption.info.yml
@@ -1,6 +1,10 @@
 name: Image Field Caption
 description: 'Provides a caption textarea for image fields.'
 package: Other
+
+dependencies:
+  - drupal:image
+
 type: module
 core_version_requirement: ^9.2 || ^10
 php: 7.1
-- 
GitLab


From 2e5f7c84145f267c1ac44c37e759d48a9aea07b7 Mon Sep 17 00:00:00 2001
From: Dieter Holvoet <dieter.holvoet@gmail.com>
Date: Mon, 9 Sep 2024 11:31:50 +0200
Subject: [PATCH 3/4] Fix cs issues

---
 README.md                   |  3 ++-
 image_field_caption.install | 19 +++++++++++++------
 image_field_caption.module  |  7 +++----
 src/ImageCaptionItem.php    |  5 ++++-
 4 files changed, 22 insertions(+), 12 deletions(-)

diff --git a/README.md b/README.md
index 8bc42d9..2c46688 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,8 @@
 
 Adds an extra text area for captions on image fields.
 Similar to the alt and title text fields available with an image field,
-the caption text area can be used to enter text or html descriptions of an image.
+the caption text area can be used to enter text or html descriptions of an 
+image.
 
 For a full description of the module, visit the
 [Image Field Caption](https://www.drupal.org/project/image_field_caption).
diff --git a/image_field_caption.install b/image_field_caption.install
index 63a974b..dab5ae6 100644
--- a/image_field_caption.install
+++ b/image_field_caption.install
@@ -1,5 +1,10 @@
 <?php
 
+/**
+ * @file
+ * Install, update and uninstall functions for the Image Field Caption module.
+ */
+
 use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
 use Drupal\Core\Entity\Sql\SqlContentEntityStorageException;
 
@@ -20,10 +25,10 @@ function image_field_caption_uninstall(): void {
 /**
  * Helper function to add new columns to a field type.
  *
- * @param $field_type
+ * @param string $field_type
  *   The field type id.
  * @param array $columns_to_add
- *   array of the column names from schema() function.
+ *   Array of the column names from schema() function.
  *
  * @see https://gist.github.com/JPustkuchen/ce53d40303a51ca5f17ce7f48c363b9b
  * @see https://www.drupal.org/project/drupal/issues/937442
@@ -60,7 +65,8 @@ function _image_field_caption_field_type_schema_column_add_helper(string $field_
       $field_name = $field_storage_definition->getName();
       try {
         $table = $table_mapping->getFieldTableName($field_name);
-      } catch (SqlContentEntityStorageException $e) {
+      }
+      catch (SqlContentEntityStorageException $e) {
         // Custom storage? Broken site? No matter what, if there is no table
         // or column, there's little we can do.
         continue;
@@ -123,10 +129,10 @@ function _image_field_caption_field_type_schema_column_add_helper(string $field_
 /**
  * Helper function to remove columns from a field type.
  *
- * @param $field_type
+ * @param string $field_type
  *   The field type id.
  * @param array $columns_to_remove
- *   array of the column names from schema() function.
+ *   Array of the column names from schema() function.
  *
  * @see https://gist.github.com/JPustkuchen/ce53d40303a51ca5f17ce7f48c363b9b
  * @see https://www.drupal.org/project/drupal/issues/937442
@@ -163,7 +169,8 @@ function _image_field_caption_field_type_schema_column_remove_helper(string $fie
       $field_name = $field_storage_definition->getName();
       try {
         $table = $table_mapping->getFieldTableName($field_name);
-      } catch (SqlContentEntityStorageException $e) {
+      }
+      catch (SqlContentEntityStorageException $e) {
         // Custom storage? Broken site? No matter what, if there is no table
         // or column, there's little we can do.
         continue;
diff --git a/image_field_caption.module b/image_field_caption.module
index 63f8999..8bae0a6 100644
--- a/image_field_caption.module
+++ b/image_field_caption.module
@@ -14,10 +14,10 @@ use Drupal\image_field_caption\ImageCaptionItem;
  * Implements hook_field_info_alter().
  */
 function image_field_caption_field_info_alter(array &$info): void {
-  // Override the image field type class
+  // Override the image field type class.
   $info['image']['class'] = ImageCaptionItem::class;
 
-  // Enable translation for the caption property
+  // Enable translation for the caption property.
   $info['image']['column_groups']['caption'] = [
     'label' => t('Caption'),
     'translatable' => TRUE,
@@ -48,8 +48,7 @@ function _image_field_caption_widget_process(array $element, FormStateInterface
   $item = $element['#value'];
   $item['fids'] = $element['fids']['#value'];
 
-  // TODO: Make #allowed_formats configurable.
-
+  // @todo Make #allowed_formats configurable.
   $element['caption'] = [
     '#title' => t('Caption'),
     '#type' => 'text_format',
diff --git a/src/ImageCaptionItem.php b/src/ImageCaptionItem.php
index 02c0ada..8fb5d92 100644
--- a/src/ImageCaptionItem.php
+++ b/src/ImageCaptionItem.php
@@ -35,6 +35,9 @@ class ImageCaptionItem extends ImageItem {
     return $settings;
   }
 
+  /**
+   * {@inheritdoc}
+   */
   public static function schema(FieldStorageDefinitionInterface $field_definition) {
     $schema = parent::schema($field_definition);
 
@@ -65,7 +68,7 @@ class ImageCaptionItem extends ImageItem {
       ->setDescription(new TranslatableMarkup("Short description of the image displayed underneath the image."));
 
     $properties['caption_format'] = DataDefinition::create('filter_format')
-        ->setLabel(new TranslatableMarkup('Text format of the caption'));
+      ->setLabel(new TranslatableMarkup('Text format of the caption'));
 
     return $properties;
   }
-- 
GitLab


From fffc01d559c45e4b43729619d5cef93916d19417 Mon Sep 17 00:00:00 2001
From: Dieter Holvoet <dieter.holvoet@gmail.com>
Date: Mon, 9 Sep 2024 11:45:46 +0200
Subject: [PATCH 4/4] Fix issue reported by phpstan

---
 src/ImageCaptionItem.php | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/ImageCaptionItem.php b/src/ImageCaptionItem.php
index 8fb5d92..3ef0772 100644
--- a/src/ImageCaptionItem.php
+++ b/src/ImageCaptionItem.php
@@ -79,6 +79,7 @@ class ImageCaptionItem extends ImageItem {
   public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
     $element = parent::fieldSettingsForm($form, $form_state);
     $settings = $this->getSettings();
+    $defaultSettings = self::defaultFieldSettings();
 
     // Add caption option.
     $element['caption_field'] = [
@@ -93,7 +94,7 @@ class ImageCaptionItem extends ImageItem {
     $element['caption_field_required'] = [
       '#type' => 'checkbox',
       '#title' => t('<em>Caption</em> field required'),
-      '#default_value' => (!empty($settings['caption_field_required'])) ? $settings['caption_field_required'] : $settings_default['caption_field_required'],
+      '#default_value' => (!empty($settings['caption_field_required'])) ? $settings['caption_field_required'] : $defaultSettings['caption_field_required'],
       '#description' => '',
       '#weight' => 14,
       '#states' => [
-- 
GitLab

