2

I have troubles adding a simple additional properties (like alt or title) to the image field type.

In the .module file I defined an alternative class for the image field.

function image_video_link_field_info_alter(&$info) {
  // Change the default widget for fields of type 'image'.
  if (isset($info['image'])) {
    $info['image']['class'] = '\Drupal\image_video_link\LinkedImageItem';
    #$info['image']['column_groups']['videolink'] = ['label' => t('Link to a video.')];
  }
}

I extended the ImageItem class with some additional properties.

/**
 * @file
 * Contains \Drupal\image_video_link\LinkedImageItem.
 */

namespace Drupal\image_video_link;

use Drupal\image\Plugin\Field\FieldType\ImageItem;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;

/**
 * Extends the default PathItem implementation to generate aliases.
 */
class LinkedImageItem extends ImageItem {
  /**
   * {@inheritdoc}
   */
  public static function defaultStorageSettings() {
    $settings = parent::defaultStorageSettings();
    $settings['default_image']['videolink'] = '';
    return $settings;
  }

  /**
   * {@inheritdoc}
   */
  public static function defaultFieldSettings() {
    $settings = parent::defaultFieldSettings();
    $settings['default_image']['videolink'] = '';
    return $settings;
  }

  /**
   * {@inheritdoc}
   */
  public static function schema(FieldStorageDefinitionInterface $field_definition) {
    $schema = parent::schema($field_definition);
    $schema['columns']['videolink'] = [
      'description' => "Link to a video.",
      'type' => 'varchar',
      'length' => 512,
    ];
    return $schema;
  }

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

    $properties['videolink'] = DataDefinition::create('string')
      ->setLabel(t('Videolink'))
      ->setDescription(t("Link to a video on Vimeo."));

    return $properties;
  }
}

I believe I have to update the database in the .install file as I am getting the following error.

Undefined property: stdClass::$field_gallery_videolink

How do I do that? Is my method even valid?

6
  • Have a look at File Entity (fieldable files). It allows you to add 'custom fields' for files / media - including images. Commented Sep 16, 2015 at 7:51
  • I already considered it, but this would require too much testing and would be simply an overkill, as I only need to extend the existing fieldtype.
    – alexej_d
    Commented Sep 16, 2015 at 20:08
  • Yes, you need to implement hook_update_N(). See this issue for reference drupal.org/node/2542748
    – jonhattan
    Commented Sep 19, 2015 at 19:22
  • Would you care to give me a code example? I already have existing entities. Do I need to save them in an object and migrate them once the new field storage is set up?
    – alexej_d
    Commented Sep 19, 2015 at 20:31
  • Sorry, writing an example of the needed code is almost solving the job. You want to write a hook_update_N() to just add a new column (with a default value) to the table of all fields of the given type. So you need to get the list of fields, and perform $schema->addField('mytable1', 'newcol', $spec); See this change record (reachable from the previous link) drupal.org/node/2554097 and also this page on how to add columns to a table: drupal.org/node/2535384
    – jonhattan
    Commented Sep 20, 2015 at 20:57

3 Answers 3

2

I know this is a older issue but I found it when trying to do the same. Still for this exactly case maybe answer from xxx would be also helpful.

After it took me a while to figure out how to do it I wanted to share this example. Only thing needs to change is the first few lines.

/**
 * Add new property to all fields of type XXX.
 */
function XXX_update_80YY() {
  $field_type = 'XXX';
  $new_properties = array(
    'PROPERTY_NAME' => array(
      'description' => 'PROPERTY DESCRIPTION',
      'type' => 'varchar',
      'length' => 255,
    ),
  );

  $definition_update_manager = \Drupal::entityDefinitionUpdateManager();
  /** @var \Drupal\Core\Entity\EntityFieldManagerInterface $field_manager */
  $field_manager = \Drupal::service('entity_field.manager');
  /** @var \Drupal\Core\Entity\EntityTypeManager $entity_type_manager */
  $entity_type_manager = \Drupal::service('entity_type.manager');
  $field_map = $field_manager->getFieldMapByFieldType($field_type);
  $schema = Database::getConnection()->schema();
  foreach ($field_map as $entity_type_id => $fields) {
    $field_manager->clearCachedFieldDefinitions();
    $storage_definitions = $field_manager->getFieldStorageDefinitions($entity_type_id);

    $storage = $entity_type_manager->getStorage($entity_type_id);
    /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
    $table_mapping = $storage->getTableMapping();

    foreach ($fields as $field_name => $field) {
      $storage_definition = $storage_definitions[$field_name];
      if (!empty($storage_definition)) {
        $column_names = $table_mapping->getColumnNames($field_name);
        $table_names = [ $table_mapping->getFieldTableName($field_name) ];

        if ($storage_definition->isRevisionable()) {
          $table_names[] = $table_mapping->getDedicatedRevisionTableName($storage_definition);
        }

        foreach ($table_names as $table_name) {
          foreach ($new_properties as $property_name => $property_spec) {
            if (!$schema->fieldExists($table_name, $column_names[$property_name])) {
              $schema->addField($table_name, $column_names[$property_name], $property_spec);
            }
          }
        }
        $definition_update_manager->updateFieldStorageDefinition($storage_definition);
      }
    }
  }
}
3
  • This didn't work for me for a field type that had one field created against a Commerce profile. I went with the helper function in gist.github.com/JPustkuchen/ce53d40303a51ca5f17ce7f48c363b9b Commented Nov 17, 2018 at 13:02
  • The last line here, re-established the old field definition...
    – Pan Chrono
    Commented Jan 20, 2020 at 12:05
  • Hi, an error here: $schema->addField($table_name, $column_names[$property_name], $property_spec); Should be: $schema->addField($table_name, $property_name, $property_spec); , $column_names[$property_name] is empty. Commented Nov 16, 2022 at 9:11
0

you should use hook_field_attach_update Hook Filed Attach Update

/**
 * Implements hook_field_attach_update().
*/
function mymodule_field_attach_update($entity_type, $entity) {
 // The other way around: you upload a new image on a node and enter    alt/title tags.
// Save alt and title text from the image field to the file entity,
// but ONLY if the file entity's alt/title fields are empty.
if ($entity_type == 'node') {  
 list(, , $bundle) = entity_extract_ids($entity_type, $entity);
// Examine every image field instance attached to this entity's bundle.
 $instances = array_intersect_key(field_info_instances($entity_type,$bundle), _file_entity_get_fields_by_type('image'));
foreach ($instances as $field_name => $instance) {
  if (!empty($entity->{$field_name})) {
    foreach ($entity->{$field_name} as $langcode => $items) {
      foreach ($items as $delta => $item) {
        $file = file_load($item['fid']);
        if ($file) {
          if (isset($file->field_file_image_alt_text) && empty($file->field_file_image_alt_text[$langcode][0]['value'])) { 
            $file->field_file_image_alt_text[$langcode][0]['value'] = $item['alt'];
          }
          if (isset($file->field_file_image_title_text) && empty($file->field_file_image_title_text[$langcode][0]['value'])) { 
            $file->field_file_image_title_text[$langcode][0]['value'] = $item['title'];
          }
        }
        field_attach_update('file', $file);
      }
    }
  }
  }
 }
}
7
  • This does not relate to drupal 8...
    – alexej_d
    Commented Sep 15, 2015 at 11:28
  • please check this link api.hostdog.eu/api/drupal-8/… Commented Sep 15, 2015 at 11:38
  • field api for 8 drupalcontrib.org/api/drupal/… Commented Sep 15, 2015 at 11:41
  • You should check the actual codebase. This hook does not exist anymore
    – alexej_d
    Commented Sep 15, 2015 at 11:42
  • I know there is a field API but I would like an answer to my question, that's why I opened the thread. I need someone to give me a hint what to do with the error and how to add the db column if it is even needed
    – alexej_d
    Commented Sep 15, 2015 at 11:45
0

Updated version custom module for Drupal 10 based on other answers. This just adds tooltip plain text property to the core link field.

PS: If you were the module owner directly then would drop the "CUSTOM EDIT" edits as the fetch from the schema and pass the property ids directly.

mylink.info.yml

name: "My link"
package: "MY PROJECT"
description: "Provides extended link functionality."
type: module
core_version_requirement: ^10
dependencies:
  - drupal:link

mylink.install

<?php

/**
 * @file
 * Install file
 *
 * Code copied from:
 *  - https://drupal.stackexchange.com/questions/174155/add-property-to-existing-field-type
 *  - https://gist.github.com/JPustkuchen/ce53d40303a51ca5f17ce7f48c363b9b
 */

/**
 * Add extra tooltip property to link fields.
 */
function mylink_install() {
  // Ensure _mylink_extra_properties_schema() can be called.
  \Drupal::service('module_handler')->loadInclude('mylink', 'module');
  _field_type_schema_column_add_helper('link', _mylink_extra_properties_schema());
}

/**
 * Remove extra tooltip property from link fields.
 */
function mylink_uninstall() {
  // Ensure _mylink_extra_properties_schema() can be called.
  \Drupal::service('module_handler')->loadInclude('mylink', 'module');
  _field_type_schema_column_remove_helper('link', _mylink_extra_properties_schema());
}


 /**
 * Helper function for HOOK_Update to update the field schema columns.
 *
 * Based on address.install (thanks to the maintainer!)
 *
 * @param $field_type The field type id.
 * @param array $columns_to_add array of the column names from schema() function.
 */
function _field_type_schema_column_add_helper($field_type, array $columns_to_add = array()) {
  $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_data) {
        $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);

        // MYLINK CUSTOM.
        //$spec = $field_schema['columns'][$column_id];
        $spec = $column_data;

        // 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)) {
          // CUSTOM EDIT.
          //$field_schema_data[$table]['fields'][$column] = $field_schema['columns'][$column_id];
          $field_schema_data[$table]['fields'][$column] = $spec;
          $field_schema_data[$table]['fields'][$column]['not null'] = FALSE;
          if ($revision_table) {
            // CUSTOM - EDIT.
            //$field_schema_data[$revision_table]['fields'][$column] = $field_schema['columns'][$column_id];
            $field_schema_data[$revision_table]['fields'][$column] = $spec;
            $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 for HOOK_Update to remove columns from the field schema.
 *
 * @param $field_type The field type id e.g. "drowl_paragraphs_settings"
 * @param array $columns_to_remove array of the column names from schema() function, e.g. ["style_textalign"]
 */
function _field_type_schema_column_remove_helper($field_type, array $columns_to_remove = array()) {
  $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_data) {
        $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);

        // CUSTOM - EDIT.
        //$spec = $field_schema['columns'][$column_id];
        $spec = $column_data;

        // 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);
        }
      }
    }
  }
}

mylink.module

<?php

/**
 * @file
 * Module file.
 */

use Drupal\mylink\Plugin\Field\FieldFormatter\MyLinkFormatter;
use Drupal\mylink\Plugin\Field\FieldType\MyLinkItem;
use Drupal\mylink\Plugin\Field\FieldWidget\MyLinkWidget;

/**
 * Extra link properties.
 */
function _mylink_extra_properties_schema($keys_only = FALSE) {
  $extra_properties = [
    'tooltip' => [
      'description' => 'Link tooltip',
      'type' => 'varchar',
      'length' => 255,
    ],
  ];
  return $extra_properties;
}

 /**
 * Implements hook_field_info_alter().
 */
function mylink_field_info_alter(&$info) {
  $info['link']['class'] = MyLinkItem::class;
}

/**
 * Implements hook_field_formatter_info_alter().
 */
function mylink_field_formatter_info_alter(&$info) {
  $info['link']['class'] = MyLinkFormatter::class;
}

/**
 * Implements hook_field_widget_info_alter().
 */
function mylink_field_widget_info_alter(&$widgets) {
  $widgets['link_default']['class'] = MyLinkWidget::class;
}

src/Plugin/Field/FieldFormatter/MyLinkFormatter.php

<?php

namespace Drupal\my_link\Plugin\Field\FieldFormatter;

use Drupal\Component\Utility\Html;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Render\Markup;
use Drupal\link\LinkItemInterface;
use Drupal\link\Plugin\Field\FieldFormatter\LinkFormatter;

/**
 * Plugin implementation of the 'link' formatter.
 */
class MyLinkFormatter extends LinkFormatter {

  /**
   * Add the tooltipto  \Drupal\Core\Url object for a link field item.
   */
  protected function buildUrl(LinkItemInterface $item) {
    $url = parent::buildUrl($item);
    $tooltip = $item->getTooltip();
    $options = $url->getOptions();

    if (!empty($tooltip)) {
      // Use markup, html tags are removed instead of escaped.
      $options['attributes']['title'] = Markup::create(Html::escape($tooltip));
    }
    $url->setOptions($options);

    return $url;
  }

}

src/Plugin/Field/FieldType/MyLinkItem.php

<?php

namespace Drupal\mylink\Plugin\Field\FieldType;

use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\link\Plugin\Field\FieldType\LinkItem;

/**
 * Extension plugin implementation of the 'link' field type.
 */
class MyLinkItem extends LinkItem {

  /**
   * {@inheritdoc}
   */
  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
    $properties = parent::propertyDefinitions($field_definition);
    $properties['tooltip'] = DataDefinition::create('string')
      ->setLabel(new TranslatableMarkup('Link tooltip'));
    return $properties;
  }

  /**
   * {@inheritdoc}
   */
  public static function schema(FieldStorageDefinitionInterface $field_definition) {
    $parent = parent::schema($field_definition);
    $parent['columns'] = $parent['columns'] +  _mylink_extra_properties_schema();
    return $parent;
  }

  /**
   * {@inheritdoc}
   */
  public function getTooltip(): ?string {
    return $this->tooltip ?: NULL;
  }

}

src/Plugin/Field/FieldWidget/MyLinkWidget.php

<?php

namespace Drupal\mylink\Plugin\Field\FieldWidget;

use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\link\Plugin\Field\FieldWidget\LinkWidget;

/**
 * Plugin implementation of the 'link' widget.
 */
class MyLinkWidget extends LinkWidget {

  /**
   * {@inheritdoc}
   */
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
    $element = parent::formElement($items, $delta, $element, $form, $form_state);
    $element['tooltip'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Tooltip'),
      '#default_value' => $items[$delta]->tooltip ?? NULL,
      '#maxlength' => 255,
      '#description' => $this->t('Note, screen readers will read this information after the link text, so if using on a link with text, do not repeat the same text.'),
    ];
    return $element;
  }

}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.