4

My module extends the existing image field type by 2 new fields, copyright and description.

FieldType plugin:

<?php

namespace Drupal\image_extra_fields\Plugin\Field\FieldType;

use Drupal\Component\Utility\Random;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\file\Entity\File;
use Drupal\file\Plugin\Field\FieldType\FileItem;
use Drupal\image\Plugin\Field\FieldType\ImageItem;

/**
 * Plugin implementation of the 'image' field type.
 *
 * @FieldType(
 *   id = "image",
 *   label = @Translation("Image"),
 *   description = @Translation("This field stores the ID of an image file as an integer value."),
 *   category = @Translation("Reference"),
 *   default_widget = "image_image",
 *   default_formatter = "image",
 *   column_groups = {
 *     "file" = {
 *       "label" = @Translation("File"),
 *       "columns" = {
 *         "target_id", "width", "height"
 *       },
 *       "require_all_groups_for_translation" = TRUE
 *     },
 *     "alt" = {
 *       "label" = @Translation("Alt"),
 *       "translatable" = TRUE
 *     },
 *     "title" = {
 *       "label" = @Translation("Title"),
 *       "translatable" = TRUE
 *     },
 *     "copyright" = {
 *       "label" = @Translation("Copyright"),
 *       "translatable" = TRUE
 *     },
 *     "description" = {
 *       "label" = @Translation("Description"),
 *       "translatable" = TRUE
 *     },
 *   },
 *   list_class = "\Drupal\file\Plugin\Field\FieldType\FileFieldItemList",
 *   constraints = {"ReferenceAccess" = {}, "FileValidation" = {}}
 * )
 */
class ExtraImageFieldsItem extends ImageItem {

  /**
   * The entity manager.
   *
   * @var \Drupal\Core\Entity\EntityManagerInterface
   */
  protected $entityManager;

  /**
   * {@inheritdoc}
   */
  public static function defaultStorageSettings() {
    return array(
      'default_image' => array(
        'uuid' => NULL,
        'alt' => '',
        'title' => '',
        'copyright' => '',
        'description' => '',
        'width' => NULL,
        'height' => NULL,
      ),
    ) + parent::defaultStorageSettings();
  }

  /**
   * {@inheritdoc}
   */
  public static function defaultFieldSettings() {
    $settings = array(
      'file_extensions' => 'png gif jpg jpeg',
      'alt_field' => 1,
      'alt_field_required' => 1,
      'title_field' => 0,
      'title_field_required' => 0,
      'copyright_field' => 0,
      'description_field' => 0,
      'max_resolution' => '',
      'min_resolution' => '',
      'default_image' => array(
        'uuid' => NULL,
        'alt' => '',
        'title' => '',
        'copyright' => '',
        'description' => '',
        'width' => NULL,
        'height' => NULL,
      ),
    ) + parent::defaultFieldSettings();

    return $settings;
  }

  /**
   * {@inheritdoc}
   */
  public static function schema(FieldStorageDefinitionInterface $field_definition) {
    $schema = parent::schema($field_definition);
    $schema['columns']['copyright'] = array(
      'description' => "Image copyright text",
      'type' => 'varchar',
      'length' => 1024,
    );
    $schema['columns']['description'] = array(
      'description' => "Image description text",
      'type' => 'text',
    );
    return $schema;
  }

  /**
   * {@inheritdoc}
   */
  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
    $properties = parent::propertyDefinitions($field_definition);

    $properties['copyright'] = DataDefinition::create('string')
      ->setLabel(t('Copyright'))
      ->setDescription(t("Image copyright text."));

    $properties['description'] = DataDefinition::create('string')
      ->setLabel(t('Description'))
      ->setDescription(t("Image description text."));

    return $properties;
  }

  /**
   * {@inheritdoc}
   */
  public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
    // Get base form from FileItem.
    $element = parent::fieldSettingsForm($form, $form_state);

    $settings = $this->getSettings();

    $element['copyright_field'] = array(
      '#type' => 'checkbox',
      '#title' => t('Enable <em>Copyright</em> field'),
      '#default_value' => $settings['copyright_field'],
      '#description' => t('The copyright text.'),
      '#weight' => 11,
    );

    $element['description_field'] = array(
      '#type' => 'checkbox',
      '#title' => t('Enable <em>Description</em> field'),
      '#default_value' => $settings['description_field'],
      '#description' => t('The description text.'),
      '#weight' => 11,
    );

    return $element;
  }

  /**
   * {@inheritdoc}
   */
  public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
    $element = parent::storageSettingsForm($form, $form_state, $has_data);
    return $element;
  }
}

And the FieldWidget plugin:

<?php

namespace Drupal\image_extra_fields\Plugin\Field\FieldWidget;

use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Form\FormStateInterface;
use Drupal\file\Entity\File;
use Drupal\file\Plugin\Field\FieldWidget\FileWidget;
use Drupal\image\Entity\ImageStyle;
use Drupal\image\Plugin\Field\FieldWidget\ImageWidget;

/**
 * Plugin implementation of the 'image_extra_fields' widget.
 *
 * @FieldWidget(
 *   id = "image_image",
 *   label = @Translation("Image Extra Fields"),
 *   field_types = {
 *     "image"
 *   }
 * )
 */
class ExtraImageFieldsWidget extends ImageWidget {

  /**
   * {@inheritdoc}
   */
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
    $element = parent::formElement($items, $delta, $element, $form, $form_state);
    $field_settings = $this->getFieldSettings();
    $element['#copyright_field'] = $field_settings['copyright_field'];
    $element['#description_field'] = $field_settings['description_field'];

    return $element;
  }

  /**
   * Form API callback: Processes a image_image field element.
   *
   * Expands the image_image type to include the alt and title fields.
   *
   * This method is assigned as a #process callback in formElement() method.
   */
  public static function process($element, FormStateInterface $form_state, $form) {
    $item = $element['#value'];
    $item['fids'] = $element['fids']['#value'];

    $element['copyright'] = array(
      '#type' => 'textfield',
      '#title' => t('Copyright'),
      '#default_value' => isset($item['copyright']) ? $item['copyright'] : '',
      '#description' => t('Image copyright text.'),
      '#maxlength' => 1024,
      '#weight' => -11,
      '#access' => (bool) $item['fids'] && $element['#copyright_field'],
    );

    $element['description'] = array(
      '#type' => 'textfield',
      '#title' => t('Description'),
      '#default_value' => isset($item['description']) ? $item['description'] : '',
      '#description' => t('Image description text.'),
      '#maxlength' => 1024,
      '#weight' => -11,
      '#access' => (bool) $item['fids'] && $element['#description_field'],
    );

    return parent::process($element, $form_state, $form);
  }
}

The 2 new fields' visibility can be set in the field settings (just like alt and title visibility). Works great so far.

But, when saving a node with data inside one of those fields of course an exception gets thrown since the column "field_name_copyright" inside the field table does not exist. So I used the function of this chap to add the missing columns to existing image field tables and I could perfectly save data! Yeay!

Problem is, I ran the mentioned function inside a simple page view, for testing purposes. Now, I need to move that update function into the module install hooks so that, once installed, all image fields receive the new columns in the database. So I wrote the following functions in .module:

/**
 * Implements hook_installed().
 */
function image_extra_fields_modules_installed($modules) {
  if (in_array('image_extra_fields', $modules)) {
    drupal_flush_all_caches();

    $field_type = 'image';
    $property_name = 'copyright';
    _image_extra_fields_update_field_definitions($field_type, $property_name, 'add');

    $field_type = 'image';
    $property_name = 'description';
    // _image_extra_fields_update_field_definitions($field_type, $property_name, 'add');
  }
}

/**
 * @todo
 */
function _image_extra_fields_update_field_definitions($field_type, $property_name, $op) {
  $manager = \Drupal::entityDefinitionUpdateManager();
  $field_map = \Drupal::entityManager()->getFieldMapByFieldType($field_type);

  foreach ($field_map as $entity_type_id => $fields) {

    foreach (array_keys($fields) as $field_name) {
      $field_storage_definition = $manager->getFieldStorageDefinition($field_name, $entity_type_id);
      $storage = \Drupal::entityTypeManager()->getStorage($entity_type_id);

      if ($storage instanceof \Drupal\Core\Entity\Sql\SqlContentEntityStorage) {
        $table_mapping = $storage->getTableMapping([
          // We pass only one storage definition to make
          // \Drupal\Core\Entity\Sql\DefaultTableMapping::getDedicatedTableNames()
          // return only tables we need.
          $field_name => $field_storage_definition,
        ]);
        $table_names = $table_mapping->getDedicatedTableNames();
        $columns = $table_mapping->getColumnNames($field_name);

        foreach ($table_names as $table_name) {
          $field_schema = $field_storage_definition->getSchema();
          $schema = \Drupal::database()->schema();

          if ('add' == $op) {
            $table_exists = $schema->tableExists($table_name);
            $field_exists = $schema->fieldExists($table_name, $columns[$property_name]);

            if (!$fieldExists && $tableExists) {
              $schema->addField($table_name, $columns[$property_name], $field_schema['columns'][$property_name]);
            }
          }

          // if ('drop' == $op) {
          //   // $schema->dropField($table_name, $columns[$property_name], $field_schema['columns'][$property_name]);
          // }
        }
      }
      $manager->updateFieldStorageDefinition($field_storage_definition);
    }
  }
}

I also tried with the install hook in the .install file, same error.

But now I get:

Uncaught PHP Exception Drupal\Core\Entity\EntityStorageException: "Exception thrown while performing a schema update. SQLSTATE[42S22]: Column not found: 1054 Unknown column 'field_slider_background_copyright' in 'where clause': SELECT 1 AS expression FROM {field_collection_item_r__edb67f8cdd} t WHERE ( (field_slider_background_target_id IS NOT NULL ) OR (field_slider_background_alt IS NOT NULL ) OR (field_slider_background_title IS NOT NULL ) OR (field_slider_background_width IS NOT NULL ) OR (field_slider_background_height IS NOT NULL ) OR (field_slider_background_copyright IS NOT NULL ) OR (field_slider_background_description IS NOT NULL ) ) LIMIT 1 OFFSET 0; Array ( ) " /core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php line 146

What am I doing wrong?

How can I add the new columns when installing my module? It looks like the update is already using the columns before actually adding them..

  • 1
    Actually, your code is defining a plugin with the same ID of an existing one. I am not sure if it is allowed, but that is not extending the existing one. – kiamlaluno Aug 6 '16 at 6:11
  • To extend an existing field, you start with hook_field_info_alter(); then you also need hook_entity_bundle_field_info_alter() to alter the fields already used from entity bundles. – kiamlaluno Aug 6 '16 at 6:41
  • @kiamlaluno the docs arent really specific about using the same id. It works for the plugins, but not for the definition updates :/ – Alex Aug 8 '16 at 7:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.