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..

  • 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 acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Browse other questions tagged or ask your own question.