Commit 6aa975ca authored by alexpott's avatar alexpott

Issue #2015701 by klausi, pfrenssen, amateescu: Convert field type to typed...

Issue #2015701 by klausi, pfrenssen, amateescu: Convert field type to typed data plugin for entity reference module.
parent 3c2bee1d
......@@ -25,7 +25,8 @@
* id = "entity_reference_field",
* label = @Translation("Entity reference field item"),
* description = @Translation("An entity field containing an entity reference."),
* list_class = "\Drupal\Core\Entity\Field\Field"
* list_class = "\Drupal\Core\Entity\Field\Field",
* constraints = {"ValidReference" = TRUE}
* )
*/
class EntityReferenceItem extends FieldItemBase {
......
<?php
/**
* @file
* Contains \Drupal\Core\Validation\Plugin\Validation\Constraint\ValidReferenceConstraint.
*/
namespace Drupal\Core\Validation\Plugin\Validation\Constraint;
use Drupal\Component\Annotation\Plugin;
use Drupal\Core\Annotation\Translation;
use Symfony\Component\Validator\Constraint;
/**
* Entity Reference valid reference constraint.
*
* Verifies that referenced entities are valid.
*
* @Plugin(
* id = "ValidReference",
* label = @Translation("Entity Reference valid reference", context = "Validation")
* )
*/
class ValidReferenceConstraint extends Constraint {
/**
* The default violation message.
*
* @var string
*/
public $message = 'The referenced entity (%type: %id) does not exist.';
}
<?php
/**
* @file
* Contains \Drupal\Core\Validation\Plugin\Validation\Constraint\ValidReferenceConstraintValidator.
*/
namespace Drupal\Core\Validation\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
/**
* Checks if referenced entities are valid.
*/
class ValidReferenceConstraintValidator extends ConstraintValidator {
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint) {
$id = $value->get('target_id')->getValue();
// '0' or NULL are considered valid empty references.
if (empty($id)) {
return;
}
$referenced_entity = $value->get('entity')->getTarget();
if (!$referenced_entity) {
$definition = $value->getDefinition();
$type = $definition['settings']['target_type'];
$this->context->addViolation($constraint->message, array('%type' => $type, '%id' => $id));
}
}
}
......@@ -47,9 +47,10 @@ protected function getTranslatorPermissions() {
* Overrides \Drupal\content_translation\Tests\ContentTranslationUITest::getNewEntityValues().
*/
protected function getNewEntityValues($langcode) {
$user = $this->drupalCreateUser();
return array(
'name' => $this->randomName(),
'user_id' => mt_rand(1, 128),
'user_id' => $user->id(),
) + parent::getNewEntityValues($langcode);
}
......
......@@ -56,9 +56,10 @@ protected function setupEntity() {
$default_langcode = $this->langcodes[0];
// Create a test entity.
$user = $this->drupalCreateUser();
$values = array(
'name' => $this->randomName(),
'user_id' => mt_rand(1, 128),
'user_id' => $user->id(),
$this->fieldName => array(array('value' => $this->randomName(16))),
);
$id = $this->createEntity($values, $default_langcode);
......
<?php
/**
* @file
* Install, update and uninstall functions for the Entity Reference
* module.
*/
/**
* Implements hook_field_schema().
*/
function entity_reference_field_schema($field) {
$schema = array(
'columns' => array(
'target_id' => array(
'description' => 'The ID of the target entity.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
),
'revision_id' => array(
'description' => 'The revision ID of the target entity.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => FALSE,
),
),
'indexes' => array(
'target_id' => array('target_id'),
),
);
// Create a foreign key to the target entity type base type.
$entity_manager = Drupal::service('plugin.manager.entity');
if (is_subclass_of($entity_manager->getControllerClass($field['settings']['target_type'], 'storage'), 'Drupal\Core\Entity\DatabaseStorageController')) {
$entity_info = $entity_manager->getDefinition($field['settings']['target_type']);
$base_table = $entity_info['base_table'];
$id_column = $entity_info['entity_keys']['id'];
$schema['foreign keys'][$base_table] = array(
'table' => $base_table,
'columns' => array('target_id' => $id_column),
);
}
return $schema;
}
......@@ -12,27 +12,14 @@
use Drupal\field\FieldInterface;
/**
* Implements hook_field_info().
* Implements hook_field_info_alter().
*/
function entity_reference_field_info() {
$field_info['entity_reference'] = array(
'label' => t('Entity Reference'),
'description' => t('This field references another entity.'),
'settings' => array(
// Default to a primary entity type (i.e. node or user).
'target_type' => module_exists('node') ? 'node' : 'user',
),
'instance_settings' => array(
// The selection handler for this instance.
'handler' => 'default',
// The handler settings.
'handler_settings' => array(),
),
'default_widget' => 'entity_reference_autocomplete',
'default_formatter' => 'entity_reference_label',
'class' => '\Drupal\entity_reference\Type\ConfigurableEntityReferenceItem',
);
return $field_info;
function entity_reference_field_info_alter(&$info) {
if (!Drupal::moduleHandler()->moduleExists('node')) {
// Fall back to another primary entity type if the Node module is not
// available.
$info['entity_reference']['settings']['target_type'] = 'user';
}
}
/**
......@@ -76,74 +63,6 @@ function entity_reference_field_widget_info_alter(&$info) {
}
}
/**
* Implements hook_field_presave().
*
* Create an entity on the fly.
*/
function entity_reference_field_presave(EntityInterface $entity, $field, $instance, $langcode, &$items) {
foreach ($items as $delta => $item) {
if (empty($item['target_id']) && !empty($item['entity']) && $item['entity']->isNew()) {
$item['entity']->save();
$items[$delta]['target_id'] = $item['entity']->id();
}
}
}
/**
* Implements hook_field_validate().
*/
function entity_reference_field_validate(EntityInterface $entity = NULL, $field, $instance, $langcode, $items, &$errors) {
$ids = array();
foreach ($items as $delta => $item) {
if (!empty($item['target_id']) && (!empty($item['entity']) && !$item['entity']->isNew())) {
$ids[$item['target_id']] = $delta;
}
}
if ($ids) {
$valid_ids = Drupal::service('plugin.manager.entity_reference.selection')->getSelectionHandler($instance, $entity)->validateReferenceableEntities(array_keys($ids));
$invalid_entities = array_diff_key($ids, array_flip($valid_ids));
if ($invalid_entities) {
foreach ($invalid_entities as $id => $delta) {
$errors[$field['field_name']][$langcode][$delta][] = array(
'error' => 'entity_reference_invalid_entity',
'message' => t('The referenced entity (@type: @id) does not exist.', array('@type' => $field['settings']['target_type'], '@id' => $id)),
);
}
}
}
}
/**
* Implements hook_field_settings_form().
*/
function entity_reference_field_settings_form($field, $instance) {
// Select the target entity type.
$entity_type_options = array();
foreach (entity_get_info() as $entity_type => $entity_info) {
// @todo As the database schema can currently only store numeric IDs of
// referenced entities and configuration entities have string IDs, prevent
// configuration entities from being referenced.
if (!is_subclass_of($entity_info['class'], '\Drupal\Core\Config\Entity\ConfigEntityInterface')) {
$entity_type_options[$entity_type] = $entity_info['label'];
}
}
$form['target_type'] = array(
'#type' => 'select',
'#title' => t('Type of item to reference'),
'#options' => $entity_type_options,
'#default_value' => $field['settings']['target_type'],
'#required' => TRUE,
'#disabled' => $field->hasData(),
'#size' => 1,
);
return $form;
}
/**
* Implements hook_ENTITY_TYPE_update() for 'field_entity'.
*
......@@ -176,79 +95,6 @@ function entity_reference_field_entity_update(FieldInterface $field) {
}
}
/**
* Implements hook_field_instance_settings_form().
*/
function entity_reference_field_instance_settings_form($field, $instance, $form_state) {
$field = isset($form_state['entity_reference']['field']) ? $form_state['entity_reference']['field'] : $field;
$instance = isset($form_state['entity_reference']['instance']) ? $form_state['entity_reference']['instance'] : $instance;
$settings = $instance['settings'];
$settings += array('handler' => 'default');
// Get all selection plugins for this entity type.
$selection_plugins = Drupal::service('plugin.manager.entity_reference.selection')->getSelectionGroups($field['settings']['target_type']);
$handler_groups = array_keys($selection_plugins);
$handlers = Drupal::service('plugin.manager.entity_reference.selection')->getDefinitions();
$handlers_options = array();
foreach ($handlers as $plugin_id => $plugin) {
// We only display base plugins (e.g. 'default', 'views', ..) and not entity
// type specific plugins (e.g. 'default_node', 'default_user', ...).
if (in_array($plugin_id, $handler_groups)) {
$handlers_options[$plugin_id] = check_plain($plugin['label']);
}
}
$form = array(
'#type' => 'container',
'#attached' => array(
'css' => array(drupal_get_path('module', 'entity_reference') . '/css/entity_reference.admin.css'),
),
'#process' => array(
'_entity_reference_field_instance_settings_ajax_process',
),
'#element_validate' => array('_entity_reference_field_instance_settings_validate'),
'#field' => $field,
'#instance' => $instance,
);
$form['handler'] = array(
'#type' => 'details',
'#title' => t('Reference type'),
'#tree' => TRUE,
'#process' => array('_entity_reference_form_process_merge_parent'),
);
$form['handler']['handler'] = array(
'#type' => 'select',
'#title' => t('Reference method'),
'#options' => $handlers_options,
'#default_value' => $settings['handler'],
'#required' => TRUE,
'#ajax' => TRUE,
'#limit_validation_errors' => array(),
);
$form['handler']['handler_submit'] = array(
'#type' => 'submit',
'#value' => t('Change handler'),
'#limit_validation_errors' => array(),
'#attributes' => array(
'class' => array('js-hide'),
),
'#submit' => array('entity_reference_settings_ajax_submit'),
);
$form['handler']['handler_settings'] = array(
'#type' => 'container',
'#attributes' => array('class' => array('entity_reference-settings')),
);
$handler = Drupal::service('plugin.manager.entity_reference.selection')->getSelectionHandler($instance);
$form['handler']['handler_settings'] += $handler->settingsForm($field, $instance);
return $form;
}
/**
* Render API callback: Processes the field instance settings form and allows
* access to the form state.
......@@ -302,21 +148,6 @@ function _entity_reference_element_validate_filter(&$element, &$form_state) {
form_set_value($element, $element['#value'], $form_state);
}
/**
* Form element validation handler; Stores the new values in the form state.
*
* @see entity_reference_field_instance_settings_form()
*/
function _entity_reference_field_instance_settings_validate($form, &$form_state) {
$instance = $form['#instance'];
if (isset($form_state['values']['instance'])) {
$instance['settings'] = $form_state['values']['instance']['settings'];
}
$form_state['entity_reference']['instance'] = $instance;
unset($form_state['values']['instance']['settings']['handler_submit']);
}
/**
* Ajax callback for the handler settings form.
*
......
......@@ -8,6 +8,7 @@
namespace Drupal\entity_reference\Plugin\Type\Selection;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Entity\Field\FieldDefinitionInterface;
/**
* A null implementation of SelectionInterface.
......@@ -15,9 +16,9 @@
class SelectionBroken implements SelectionInterface {
/**
* Implements SelectionInterface::settingsForm().
* {@inheritdoc}
*/
public static function settingsForm(&$field, &$instance) {
public static function settingsForm(FieldDefinitionInterface $field_definition) {
$form['selection_handler'] = array(
'#markup' => t('The selected selection handler is broken.'),
);
......@@ -25,33 +26,34 @@ public static function settingsForm(&$field, &$instance) {
}
/**
* Implements SelectionInterface::getReferenceableEntities().
* {@inheritdoc}
*/
public function getReferenceableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) {
return array();
}
/**
* Implements SelectionInterface::countReferenceableEntities().
* {@inheritdoc}
*/
public function countReferenceableEntities($match = NULL, $match_operator = 'CONTAINS') {
return 0;
}
/**
* Implements SelectionInterface::validateReferenceableEntities().
* {@inheritdoc}
*/
public function validateReferenceableEntities(array $ids) {
return array();
}
/**
* Implements SelectionInterface::validateAutocompleteInput().
* {@inheritdoc}
*/
public function validateAutocompleteInput($input, &$element, &$form_state, $form, $strict = TRUE) { }
/**
* Implements SelectionInterface::entityQueryAlter().
* {@inheritdoc}
*/
public function entityQueryAlter(SelectInterface $query) { }
}
......@@ -8,6 +8,7 @@
namespace Drupal\entity_reference\Plugin\Type\Selection;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Entity\Field\FieldDefinitionInterface;
/**
* Interface definition for Entity Reference selection plugins.
......@@ -73,13 +74,12 @@ public function entityQueryAlter(SelectInterface $query);
/**
* Generates the settings form for this selection.
*
* @param array $field
* A field data structure.
* @param array $instance
* A field instance data structure.
* @param \Drupal\Core\Entity\Field\FieldDefinitionInterface $field_definition
* The definition of the field to which the selection is associated.
*
* @return array
* A Form API array.
*/
public static function settingsForm(&$field, &$instance);
public static function settingsForm(FieldDefinitionInterface $field_definition);
}
......@@ -54,15 +54,14 @@ public function __construct(FieldDefinitionInterface $field_definition, EntityIn
/**
* {@inheritdoc}
*/
public static function settingsForm(&$field, &$instance) {
$entity_info = entity_get_info($field['settings']['target_type']);
$bundles = entity_get_bundles($field['settings']['target_type']);
public static function settingsForm(FieldDefinitionInterface $field_definition) {
$target_type = $field_definition->getFieldSetting('target_type');
$selection_handler_settings = $field_definition->getFieldSetting('handler_settings') ?: array();
$entity_info = \Drupal::entityManager()->getDefinition($target_type);
$bundles = entity_get_bundles($target_type);
// Merge-in default values.
if (!isset($instance['settings']['handler_settings'])) {
$instance['settings']['handler_settings'] = array();
}
$instance['settings']['handler_settings'] += array(
$selection_handler_settings += array(
'target_bundles' => array(),
'sort' => array(
'field' => '_none',
......@@ -78,10 +77,10 @@ public static function settingsForm(&$field, &$instance) {
$target_bundles_title = t('Bundles');
// Default core entity types with sensible labels.
if ($field['settings']['target_type'] == 'node') {
if ($target_type == 'node') {
$target_bundles_title = t('Content types');
}
elseif ($field['settings']['target_type'] == 'taxonomy_term') {
elseif ($target_type == 'taxonomy_term') {
$target_bundles_title = t('Vocabularies');
}
......@@ -89,7 +88,7 @@ public static function settingsForm(&$field, &$instance) {
'#type' => 'checkboxes',
'#title' => $target_bundles_title,
'#options' => $bundle_options,
'#default_value' => (!empty($instance['settings']['handler_settings']['target_bundles'])) ? $instance['settings']['handler_settings']['target_bundles'] : array(),
'#default_value' => (!empty($selection_handler_settings['target_bundles'])) ? $selection_handler_settings['target_bundles'] : array(),
'#required' => TRUE,
'#size' => 6,
'#multiple' => TRUE,
......@@ -106,7 +105,7 @@ public static function settingsForm(&$field, &$instance) {
// @todo Use Entity::getPropertyDefinitions() when all entity types are
// converted to the new Field API.
$fields = drupal_map_assoc(drupal_schema_fields_sql($entity_info['base_table']));
foreach (field_info_instances($field['settings']['target_type']) as $bundle_instances) {
foreach (field_info_instances($target_type) as $bundle_instances) {
foreach ($bundle_instances as $instance_name => $instance_info) {
$field_info = field_info_field($instance_name);
foreach ($field_info['columns'] as $column_name => $column_info) {
......@@ -123,7 +122,7 @@ public static function settingsForm(&$field, &$instance) {
) + $fields,
'#ajax' => TRUE,
'#limit_validation_errors' => array(),
'#default_value' => $instance['settings']['handler_settings']['sort']['field'],
'#default_value' => $selection_handler_settings['sort']['field'],
);
$form['sort']['settings'] = array(
......@@ -132,9 +131,9 @@ public static function settingsForm(&$field, &$instance) {
'#process' => array('_entity_reference_form_process_merge_parent'),
);
if ($instance['settings']['handler_settings']['sort']['field'] != '_none') {
if ($selection_handler_settings['sort']['field'] != '_none') {
// Merge-in default values.
$instance['settings']['handler_settings']['sort'] += array(
$selection_handler_settings['sort'] += array(
'direction' => 'ASC',
);
......@@ -146,7 +145,7 @@ public static function settingsForm(&$field, &$instance) {
'ASC' => t('Ascending'),
'DESC' => t('Descending'),
),
'#default_value' => $instance['settings']['handler_settings']['sort']['direction'],
'#default_value' => $selection_handler_settings['sort']['direction'],
);
}
......
<?php
/**
* @file
* Contains \Drupal\entity_reference\Plugin\field\field_type\ConfigurableEntityReferenceItem.
*/
namespace Drupal\entity_reference\Plugin\field\field_type;
use Drupal\Core\Annotation\Translation;
use Drupal\Core\Entity\Annotation\FieldType;
use Drupal\Core\Entity\Field\Type\EntityReferenceItem;
use Drupal\field\Plugin\Type\FieldType\ConfigEntityReferenceItemBase;
use Drupal\field\Plugin\Type\FieldType\ConfigFieldItemBase;
use Drupal\field\Plugin\Type\FieldType\ConfigFieldItemInterface;
use Drupal\field\FieldInterface;
/**
* Plugin implementation of the 'entity_reference' field type.
*
* @FieldType(
* id = "entity_reference",
* label = @Translation("Entity Reference"),
* description = @Translation("This field references another entity."),
* settings = {
* "target_type" = "node"
* },
* instance_settings = {
* "handler" = "default",
* "handler_settings" = { }
* },
* default_widget = "entity_reference_autocomplete",
* default_formatter = "entity_reference_label",
* constraints = {"ValidReference" = TRUE}
* )
*
* Extends the Core 'entity_reference' entity field item with properties for
* revision ids, labels (for autocreate) and access.
*
* Required settings (below the definition's 'settings' key) are:
* - target_type: The entity type to reference.
*/
class ConfigurableEntityReferenceItem extends ConfigEntityReferenceItemBase implements ConfigFieldItemInterface {
/**
* {@inheritdoc}
*/
public static function schema(FieldInterface $field) {
$schema = array(
'columns' => array(
'target_id' => array(
'description' => 'The ID of the target entity.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
),
'revision_id' => array(
'description' => 'The revision ID of the target entity.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => FALSE,
),
),
'indexes' => array(
'target_id' => array('target_id'),
),
);
// Create a foreign key to the target entity type base type.
$entity_manager = \Drupal::service('plugin.manager.entity');
if (is_subclass_of($entity_manager->getControllerClass($field['settings']['target_type'], 'storage'), 'Drupal\Core\Entity\DatabaseStorageController')) {
$entity_info = $entity_manager->getDefinition($field['settings']['target_type']);
$base_table = $entity_info['base_table'];
$id_column = $entity_info['entity_keys']['id'];
$schema['foreign keys'][$base_table] = array(
'table' => $base_table,
'columns' => array('target_id' => $id_column),
);
}
return $schema;
}
/**
* {@inheritdoc}
*/
public function preSave() {
$entity = $this->get('entity')->getValue();
$target_id = $this->get('target_id')->getValue();
if (empty($target_id) && !empty($entity) && $entity->isNew()) {
$entity->save();
$this->set('target_id', $entity->id());
}
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, array &$form_state, $has_data) {
// Select the target entity type.