Commit 15fded0c authored by catch's avatar catch

Issue #1988612 by effulgentsia, yched, Wim Leers, Berdir, Pancho: Apply...

Issue #1988612 by effulgentsia, yched, Wim Leers, Berdir, Pancho: Apply formatters and widgets to rendered entity base fields, starting with node.title.
parent b20755f4
......@@ -708,6 +708,14 @@ function entity_get_render_display(EntityInterface $entity, $view_mode) {
$display = entity_get_display($entity_type, $bundle, $render_view_mode);
$display->originalMode = $view_mode;
// Let modules alter the display.
$display_context = array(
'entity_type' => $entity_type,
'bundle' => $bundle,
'view_mode' => $view_mode,
);
drupal_alter('entity_display', $display, $display_context);
return $display;
}
......@@ -808,6 +816,14 @@ function entity_get_render_form_display(EntityInterface $entity, $form_mode) {
$form_display = entity_get_form_display($entity_type, $bundle, $render_form_mode);
$form_display->originalMode = $form_mode;
// Let modules alter the form display.
$form_display_context = array(
'entity_type' => $entity_type,
'bundle' => $bundle,
'form_mode' => $form_mode,
);
drupal_alter('entity_form_display', $form_display, $form_display_context);
return $form_display;
}
......
......@@ -125,15 +125,6 @@ protected function init(array &$form_state) {
$this->prepareEntity();
$form_display = entity_get_render_form_display($this->entity, $this->getOperation());
// Let modules alter the form display.
$form_display_context = array(
'entity_type' => $this->entity->entityType(),
'bundle' => $this->entity->bundle(),
'form_mode' => $this->getOperation(),
);
$this->moduleHandler->alter('entity_form_display', $form_display, $form_display_context);
$this->setFormDisplay($form_display, $form_state);
// Invoke the prepare form hooks.
......
......@@ -208,20 +208,9 @@ public function viewMultiple(array $entities = array(), $view_mode = 'full', $la
// Store entities for rendering by view_mode.
$view_modes[$entity_view_mode][$entity->id()] = $entity;
// Load the corresponding display settings if not stored yet.
// Get the corresponding display settings.
if (!isset($displays[$entity_view_mode][$bundle])) {
// Get the display object for this bundle and view mode.
$display = entity_get_render_display($entity, $entity_view_mode);
// Let modules alter the display.
$display_context = array(
'entity_type' => $this->entityType,
'bundle' => $bundle,
'view_mode' => $entity_view_mode,
);
drupal_alter('entity_display', $display, $display_context);
$displays[$entity_view_mode][$bundle] = $display;
$displays[$entity_view_mode][$bundle] = entity_get_render_display($entity, $entity_view_mode);
}
}
......
......@@ -328,9 +328,24 @@ public function flagErrors(FieldItemListInterface $items, array $form, array &$f
$field_state = field_form_get_state($form['#parents'], $field_name, $form_state);
if (!empty($field_state['constraint_violations'])) {
$form_builder = \Drupal::formBuilder();
// Locate the correct element in the the form.
$element = NestedArray::getValue($form_state['complete_form'], $field_state['array_parents']);
// Do not report entity-level validation errors if Form API errors have
// already been reported for the field.
// @todo Field validation should not be run on fields with FAPI errors to
// begin with. See https://drupal.org/node/2070429.
$element_path = implode('][', $element['#parents']);
if ($reported_errors = $form_builder->getErrors($form_state)) {
foreach (array_keys($reported_errors) as $error_path) {
if (strpos($error_path, $element_path) === 0) {
return;
}
}
}
// Only set errors if the element is accessible.
if (!isset($element['#access']) || $element['#access']) {
$definition = $this->getPluginDefinition();
......@@ -341,16 +356,22 @@ public function flagErrors(FieldItemListInterface $items, array $form, array &$f
// Separate violations by delta.
$property_path = explode('.', $violation->getPropertyPath());
$delta = array_shift($property_path);
$violation->arrayPropertyPath = $property_path;
// Violations at the ItemList level are not associated to any delta,
// we file them under $delta NULL.
$delta = is_numeric($delta) ? $delta : NULL;
$violations_by_delta[$delta][] = $violation;
$violation->arrayPropertyPath = $property_path;
}
foreach ($violations_by_delta as $delta => $delta_violations) {
// For a multiple-value widget, pass all errors to the main widget.
// For single-value widgets, pass errors by delta.
if ($is_multiple) {
// Pass violations to the main element:
// - if this is a multiple-value widget,
// - or if the violations are at the ItemList level.
if ($is_multiple || $delta === NULL) {
$delta_element = $element;
}
// Otherwise, pass errors by delta to the corresponding sub-element.
else {
$original_delta = $field_state['original_deltas'][$delta];
$delta_element = $element[$original_delta];
......@@ -359,7 +380,7 @@ public function flagErrors(FieldItemListInterface $items, array $form, array &$f
// @todo: Pass $violation->arrayPropertyPath as property path.
$error_element = $this->errorElement($delta_element, $violation, $form, $form_state);
if ($error_element !== FALSE) {
form_error($error_element, $form_state, $violation->getMessage());
$form_builder->setError($error_element, $form_state, $violation->getMessage());
}
}
}
......
......@@ -20,6 +20,9 @@ class AllowedValuesConstraintValidator extends ChoiceValidator {
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint) {
if (!isset($value)) {
return;
}
if ($this->context->getMetadata()->getTypedData() instanceof AllowedValuesInterface) {
$account = \Drupal::currentUser();
$allowed_values = $this->context->getMetadata()->getTypedData()->getSettableValues($account);
......
......@@ -359,7 +359,7 @@ function createSampleNodes($count = 5) {
// Post $count article nodes.
for ($i = 0; $i < $count; $i++) {
$edit = array();
$edit['title'] = $this->randomName();
$edit['title[0][value]'] = $this->randomName();
$edit['body[0][value]'] = $this->randomName();
$this->drupalPostForm('node/add/article', $edit, t('Save'));
}
......
......@@ -243,7 +243,7 @@ function createBookNode($book_nid, $parent = NULL) {
static $number = 0; // Used to ensure that when sorted nodes stay in same order.
$edit = array();
$edit["title"] = $number . ' - SimpleTest test node ' . $this->randomName(10);
$edit['title[0][value]'] = $number . ' - SimpleTest test node ' . $this->randomName(10);
$edit['body[0][value]'] = 'SimpleTest test body ' . $this->randomName(32) . ' ' . $this->randomName(32);
$edit['book[bid]'] = $book_nid;
......@@ -258,7 +258,7 @@ function createBookNode($book_nid, $parent = NULL) {
}
// Check to make sure the book node was created.
$node = $this->drupalGetNodeByTitle($edit['title']);
$node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
$this->assertNotNull(($node === FALSE ? NULL : $node), 'Book node found in database.');
$number++;
......
......@@ -95,7 +95,7 @@ function testCommentLanguage() {
// Create "Article" content.
$title = $this->randomName();
$edit = array(
'title' => $title,
'title[0][value]' => $title,
'body[0][value]' => $this->randomName(),
'langcode' => $node_langcode,
'comment[0][status]' => COMMENT_OPEN,
......
......@@ -309,7 +309,7 @@ private function doNode($type) {
// Create a node using the form in order to generate an add content event
// (which is not triggered by drupalCreateNode).
$edit = $this->getContent($type);
$title = $edit["title"];
$title = $edit['title[0][value]'];
$this->drupalPostForm('node/add/' . $type, $edit, t('Save'));
$this->assertResponse(200);
// Retrieve the node object.
......@@ -369,7 +369,7 @@ private function getContent($type) {
switch ($type) {
case 'forum':
$content = array(
'title' => $this->randomName(8),
'title[0][value]' => $this->randomName(8),
'taxonomy_forums' => array(1),
'body[0][value]' => $this->randomName(32),
);
......@@ -377,7 +377,7 @@ private function getContent($type) {
default:
$content = array(
'title' => $this->randomName(8),
'title[0][value]' => $this->randomName(8),
'body[0][value]' => $this->randomName(32),
);
break;
......
......@@ -4,7 +4,7 @@ services:
arguments: ['@container.namespaces']
access_check.edit.entity_field:
class: Drupal\edit\Access\EditEntityFieldAccessCheck
arguments: ['@entity.manager', '@field.info']
arguments: ['@entity.manager']
tags:
- { name: access_check }
access_check.edit.entity:
......
......@@ -21,7 +21,13 @@ Drupal.edit.editors.plain_text = Drupal.edit.EditorView.extend({
var fieldModel = this.fieldModel;
// Store the original value of this field. Necessary for reverting changes.
var $textElement = this.$textElement = this.$el.find('.field-item:first');
var $textElement;
if (this.$el.is(':has(.field-item)')) {
$textElement = this.$textElement = this.$el.find('.field-item:first');
}
else {
$textElement = this.$textElement = this.$el;
}
editorModel.set('originalValue', $.trim(this.$textElement.text()));
// Sets the state to 'changed' whenever the value changes
......
......@@ -15,7 +15,6 @@
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Drupal\Core\Entity\EntityInterface;
use Drupal\field\FieldInfo;
/**
* Access check for editing entity fields.
......@@ -29,24 +28,14 @@ class EditEntityFieldAccessCheck implements StaticAccessCheckInterface, EditEnti
*/
protected $entityManager;
/**
* The field info.
*
* @var \Drupal\field\FieldInfo
*/
protected $fieldInfo;
/**
* Constructs a EditEntityFieldAccessCheck object.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\field\FieldInfo $field_info
* The field info.
*/
public function __construct(EntityManagerInterface $entity_manager, FieldInfo $field_info) {
public function __construct(EntityManagerInterface $entity_manager) {
$this->entityManager = $entity_manager;
$this->fieldInfo = $field_info;
}
/**
......@@ -95,7 +84,7 @@ protected function validateAndUpcastRequestAttributes(Request $request) {
// Validate the field name and language.
$field_name = $request->attributes->get('field_name');
if (!$field_name || !$this->fieldInfo->getInstance($entity->entityType(), $entity->bundle(), $field_name)) {
if (!$field_name || !$entity->hasField($field_name)) {
throw new NotFoundHttpException();
}
$langcode = $request->attributes->get('langcode');
......
......@@ -145,7 +145,7 @@ public function metadata(Request $request) {
}
// Validate the field name and language.
if (!$field_name || !($instance = $this->fieldInfo->getInstance($entity->entityType(), $entity->bundle(), $field_name))) {
if (!$field_name || !$entity->hasField($field_name)) {
throw new NotFoundHttpException();
}
if (!$langcode || (field_valid_language($langcode) !== $langcode)) {
......@@ -158,7 +158,8 @@ public function metadata(Request $request) {
$metadata[$entity_id] = $this->metadataGenerator->generateEntity($entity, $langcode);
}
$metadata[$field] = $this->metadataGenerator->generateField($entity, $instance, $langcode, $view_mode);
$field_definition = $entity->get($field_name)->getFieldDefinition();
$metadata[$field] = $this->metadataGenerator->generateField($entity, $field_definition, $langcode, $view_mode);
}
return new JsonResponse($metadata);
......
......@@ -128,18 +128,8 @@ protected function init(array &$form_state, EntityInterface $entity, $field_name
$form_state['field_name'] = $field_name;
// @todo Allow the usage of different form modes by exposing a hook and the
// UI for them.
$form_display = entity_get_render_form_display($entity, 'default');
// Let modules alter the form display.
$form_display_context = array(
'entity_type' => $entity->entityType(),
'bundle' => $entity->bundle(),
'form_mode' => 'default',
);
$this->moduleHandler->alter('entity_form_display', $form_display, $form_display_context);
$form_state['form_display'] = $form_display;
// UI for them.
$form_state['form_display'] = entity_get_render_form_display($entity, 'default');
}
/**
......@@ -180,14 +170,14 @@ public function submitForm(array &$form, array &$form_state) {
*/
protected function buildEntity(array $form, array &$form_state) {
$entity = clone $form_state['entity'];
$field_name = $form_state['field_name'];
field_attach_extract_form_values($entity, $form, $form_state, array('field_name' => $form_state['field_name']));
field_attach_extract_form_values($entity, $form, $form_state, array('field_name' => $field_name));
// @todo Refine automated log messages and abstract them to all entity
// types: http://drupal.org/node/1678002.
if ($entity->entityType() == 'node' && $entity->isNewRevision() && !isset($entity->log)) {
$instance = field_info_instance($entity->entityType(), $form_state['field_name'], $entity->bundle());
$entity->log = t('Updated the %field-name field through in-place editing.', array('%field-name' => $instance->getFieldLabel()));
$entity->log = t('Updated the %field-name field through in-place editing.', array('%field-name' => $entity->get($field_name)->getFieldDefinition()->getFieldLabel()));
}
return $entity;
......
......@@ -40,13 +40,6 @@ class EditEntityFieldAccessCheckTest extends UnitTestCase {
*/
protected $entityManager;
/**
* The mocked field info.
*
* @var \Drupal\field\FieldInfo|\PHPUnit_Framework_MockObject_MockObject
*/
protected $fieldInfo;
/**
* The mocked entity storage controller.
*
......@@ -71,11 +64,7 @@ protected function setUp() {
->method('getStorageController')
->will($this->returnValue($this->entityStorageController));
$this->fieldInfo = $this->getMockBuilder('Drupal\field\FieldInfo')
->disableOriginalConstructor()
->getMock();
$this->editAccessCheck = new EditEntityFieldAccessCheck($this->entityManager, $this->fieldInfo);
$this->editAccessCheck = new EditEntityFieldAccessCheck($this->entityManager);
}
/**
......@@ -91,16 +80,12 @@ public function testAppliesTo() {
* @see \Drupal\edit\Tests\edit\Access\EditEntityFieldAccessCheckTest::testAccess()
*/
public function providerTestAccess() {
$editable_entity = $this->getMockBuilder('Drupal\entity_test\Entity\EntityTest')
->disableOriginalConstructor()
->getMock();
$editable_entity = $this->createMockEntity();
$editable_entity->expects($this->any())
->method('access')
->will($this->returnValue(TRUE));
$non_editable_entity = $this->getMockBuilder('Drupal\entity_test\Entity\EntityTest')
->disableOriginalConstructor()
->getMock();
$non_editable_entity = $this->createMockEntity();
$non_editable_entity->expects($this->any())
->method('access')
->will($this->returnValue(FALSE));
......@@ -146,22 +131,15 @@ public function testAccess(EntityInterface $entity, FieldInterface $field = NULL
$entity_with_field = clone $entity;
$entity_with_field->expects($this->any())
->method('get')
->with('valid')
->will($this->returnValue($field));
// Prepare the request to be valid.
$request->attributes->set('entity', $entity_with_field);
$request->attributes->set('entity_type', 'test_entity');
$request->attributes->set('field_name', 'example');
$request->attributes->set('entity', $entity_with_field);
$request->attributes->set('field_name', 'valid');
$request->attributes->set('langcode', Language::LANGCODE_NOT_SPECIFIED);
$this->fieldInfo->expects($this->any())
->method('getInstance')
->will($this->returnValue(array(
'example' => array(
'field_name' => 'example',
)
)));
$account = $this->getMock('Drupal\Core\Session\AccountInterface');
$access = $this->editAccessCheck->access($route, $request, $account);
$this->assertSame($expected_result, $access);
......@@ -220,12 +198,7 @@ public function testAccessWithNotPassedFieldName() {
$route = new Route('/edit/form/test_entity/1/body/und/full', array(), array('_access_edit_entity_field' => 'TRUE'));
$request = new Request();
$request->attributes->set('entity_type', 'entity_test');
$entity = $this->getMockBuilder('Drupal\entity_test\Entity\EntityTest')
->disableOriginalConstructor()
->getMock();
$request->attributes->set('entity', $entity);
$request->attributes->set('entity', $this->createMockEntity());
$account = $this->getMock('Drupal\Core\Session\AccountInterface');
$this->editAccessCheck->access($route, $request, $account);
......@@ -240,25 +213,9 @@ public function testAccessWithNonExistingField() {
$route = new Route('/edit/form/test_entity/1/body/und/full', array(), array('_access_edit_entity_field' => 'TRUE'));
$request = new Request();
$request->attributes->set('entity_type', 'entity_test');
$entity = $this->getMockBuilder('Drupal\entity_test\Entity\EntityTest')
->disableOriginalConstructor()
->getMock();
$entity->expects($this->any())
->method('entityType')
->will($this->returnValue('entity_test'));
$entity->expects($this->any())
->method('bundle')
->will($this->returnValue('test_bundle'));
$request->attributes->set('entity', $entity);
$request->attributes->set('entity', $this->createMockEntity());
$request->attributes->set('field_name', 'not_valid');
$this->fieldInfo->expects($this->once())
->method('getInstance')
->with('entity_test', 'test_bundle', 'not_valid')
->will($this->returnValue(NULL));
$account = $this->getMock('Drupal\Core\Session\AccountInterface');
$this->editAccessCheck->access($route, $request, $account);
}
......@@ -272,22 +229,9 @@ public function testAccessWithNotPassedLanguage() {
$route = new Route('/edit/form/test_entity/1/body/und/full', array(), array('_access_edit_entity_field' => 'TRUE'));
$request = new Request();
$request->attributes->set('entity_type', 'entity_test');
$entity = $this->getMockBuilder('Drupal\entity_test\Entity\EntityTest')
->disableOriginalConstructor()
->getMock();
$request->attributes->set('entity', $entity);
$request->attributes->set('entity', $this->createMockEntity());
$request->attributes->set('field_name', 'valid');
$field = $this->getMockBuilder('Drupal\field\Entity\Field')
->disableOriginalConstructor()
->getMock();
$this->fieldInfo->expects($this->once())
->method('getInstance')
->will($this->returnValue($field));
$account = $this->getMock('Drupal\Core\Session\AccountInterface');
$this->editAccessCheck->access($route, $request, $account);
}
......@@ -301,25 +245,30 @@ public function testAccessWithInvalidLanguage() {
$route = new Route('/edit/form/test_entity/1/body/und/full', array(), array('_access_edit_entity_field' => 'TRUE'));
$request = new Request();
$request->attributes->set('entity_type', 'entity_test');
$entity = $this->getMockBuilder('Drupal\entity_test\Entity\EntityTest')
->disableOriginalConstructor()
->getMock();
$request->attributes->set('entity', $entity);
$request->attributes->set('entity', $this->createMockEntity());
$request->attributes->set('field_name', 'valid');
$request->attributes->set('langcode', 'xx-lolspeak');
$field = $this->getMockBuilder('Drupal\field\Entity\Field')
$account = $this->getMock('Drupal\Core\Session\AccountInterface');
$this->editAccessCheck->access($route, $request, $account);
}
/**
* Returns a mock entity.
*/
protected function createMockEntity() {
$entity = $this->getMockBuilder('Drupal\entity_test\Entity\EntityTest')
->disableOriginalConstructor()
->getMock();
$this->fieldInfo->expects($this->once())
->method('getInstance')
->will($this->returnValue($field));
$entity->expects($this->any())
->method('hasField')
->will($this->returnValueMap(array(
array('valid', TRUE),
array('not_valid', FALSE),
)));
$account = $this->getMock('Drupal\Core\Session\AccountInterface');
$this->editAccessCheck->access($route, $request, $account);
return $entity;
}
}
......
......@@ -51,10 +51,9 @@ public function getRenderer($field_name) {
}
// Instantiate the formatter object from the stored display properties.
if ($configuration = $this->getComponent($field_name)) {
$instance = field_info_instance($this->targetEntityType, $field_name, $this->bundle);
if (($configuration = $this->getComponent($field_name)) && isset($configuration['type']) && ($definition = $this->getFieldDefinition($field_name))) {
$formatter = $this->pluginManager->getInstance(array(
'field_definition' => $instance,
'field_definition' => $definition,
'view_mode' => $this->originalMode,
// No need to prepare, defaults have been merged in setComponent().
'prepare' => FALSE,
......
......@@ -51,10 +51,9 @@ public function getRenderer($field_name) {
}
// Instantiate the widget object from the stored display properties.
if ($configuration = $this->getComponent($field_name)) {
$instance = field_info_instance($this->targetEntityType, $field_name, $this->bundle);
if (($configuration = $this->getComponent($field_name)) && isset($configuration['type']) && ($definition = $this->getFieldDefinition($field_name))) {
$widget = $this->pluginManager->getInstance(array(
'field_definition' => $instance,
'field_definition' => $definition,
'form_mode' => $this->originalMode,
// No need to prepare, defaults have been merged in setComponent().
'prepare' => FALSE,
......
......@@ -8,6 +8,7 @@
namespace Drupal\entity;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\ContentEntityInterface;
/**
* Base class for config entity types that store configuration for entity forms
......@@ -43,6 +44,17 @@
*/
public $bundle;
/**
* A partial entity, created via _field_create_entity_from_ids() from
* $targetEntityType and $bundle.
*
* @var \Drupal\Core\Entity\EntityInterface
*
* @todo Remove when getFieldDefinition() is fixed to not need it.
* https://drupal.org/node/2114707
*/
private $targetEntity;
/**
* View or form mode to be displayed.
*
......@@ -187,9 +199,22 @@ public function getComponents() {
* {@inheritdoc}
*/
public function getComponent($name) {
// We always store 'extra fields', whether they are visible or hidden.
// Until https://drupal.org/node/2144919 allows base fields to be configured
// in the UI, many base fields are also still registered as "extra fields"
// to keep appearing in the "Manage (form) display" screens.
// - Field UI still treats them as "base fields", saving only the weight
// and visibility flag in the EntityDisplay.
// - For some of them (e.g. node title), the custom rendering code has been
// removed in favor of regular widgets/formatters. Their display options
// are "upgraded" to those of a field (widget/formatter + settings) at
// runtime using hook_entity_display_alter().
// The getComponent() / setComponent() methods handle this by treating
// components as "extra fields" if they are registered as such, *and* if
// their display options contain no 'type' entry specifying a widget or
// formatter.
// @todo Cleanup after https://drupal.org/node/2144919 is fixed.
$extra_fields = field_info_extra_fields($this->targetEntityType, $this->bundle, $this->displayContext);
if (isset($extra_fields[$name])) {
if (isset($extra_fields[$name]) && !isset($this->content[$name]['type'])) {
// If we have explicit settings, return an array or NULL depending on
// visibility.
if (isset($this->content[$name])) {
......@@ -215,8 +240,7 @@ public function getComponent($name) {
return NULL;
}
}
if (isset($this->content[$name])) {
elseif (isset($this->content[$name])) {
return $this->content[$name];
}
}
......@@ -231,18 +255,18 @@ public function setComponent($name, array $options = array()) {
$options['weight'] = isset($max) ? $max + 1 : 0;
}
if ($instance = field_info_instance($this->targetEntityType, $name, $this->bundle)) {
$options = $this->pluginManager->prepareConfiguration($instance->getFieldType(), $options);
// Clear the persisted plugin, if any.
unset($this->plugins[$name]);
}
// We always store 'extra fields', whether they are visible or hidden.
// See remark in getComponent().
// @todo Cleanup after https://drupal.org/node/2144919 is fixed.
$extra_fields = field_info_extra_fields($this->targetEntityType, $this->bundle, $this->displayContext);
if (isset($extra_fields[$name])) {
if (isset($extra_fields[$name]) && !isset($options['type'])) {
$options['visible'] = TRUE;
}
elseif ($field_definition = $this->getFieldDefinition($name)) {
$options = $this->pluginManager->prepareConfiguration($field_definition->getFieldType(), $options);
}
// Clear the persisted plugin, if any.
unset($this->plugins[$name]);
$this->content[$name] = $options;
......@@ -253,8 +277,10 @@ public function setComponent($name, array $options = array()) {
* {@inheritdoc}
*/
public function removeComponent($name) {
// See remark in getComponent().
// @todo Cleanup after https://drupal.org/node/2144919 is fixed.
$extra_fields = field_info_extra_fields($this->targetEntityType, $this->bundle, $this->displayContext);
if (isset($extra_fields[$name])) {
if (isset($extra_fields[$name]) && !isset($this->content[$name]['type'])) {
// 'Extra fields' are exposed in hooks and can appear at any given time.
// Therefore we store extra fields that are explicitly being hidden, so
// that we can differenciate with those that are simply not configured
......@@ -265,9 +291,10 @@ public function removeComponent($name) {
}
else {
unset($this->content[$name]);
unset($this->plugins[$name]);
}
unset($this->plugins[$name]);
return $this;
}
......@@ -290,4 +317,18 @@ public function getHighestWeight() {
return $weights ? max($weights) : NULL;
}
/**
* Returns the field definition of a field.
*/
protected function getFieldDefinition($field_name) {
// @todo Replace this entire implementation with
// \Drupal::entityManager()->getFieldDefinition() when it can hand the
// $instance objects - https://drupal.org/node/2114707
if (!isset($this->targetEntity)) {
$this->targetEntity = _field_create_entity_from_ids((object) array('entity_type' => $this->targetEntityType, 'bundle' => $this->bundle, 'entity_id' => NULL));
}
if (($this->targetEntity instanceof ContentEntityInterface) && $this->targetEntity->hasField($field_name)) {
return $this->targetEntity->get($field_name)->getFieldDefinition();
}