diff --git a/core/lib/Drupal/Core/Entity/Field/Field.php b/core/lib/Drupal/Core/Entity/Field/Field.php index 7cf493c..510c5cf 100644 --- a/core/lib/Drupal/Core/Entity/Field/Field.php +++ b/core/lib/Drupal/Core/Entity/Field/Field.php @@ -7,6 +7,7 @@ namespace Drupal\Core\Entity\Field; +use Drupal\Core\Entity\Field\FieldDefinition; use Drupal\Core\Entity\Field\FieldInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\TypedData\TypedDataInterface; @@ -37,6 +38,13 @@ class Field extends ItemList implements FieldInterface { protected $list = array(); /** + * The field definition. + * + * @var \Drupal\Core\Entity\Field\FieldDefinitionInterface + */ + protected $fieldDefinition; + + /** * Overrides TypedData::__construct(). */ public function __construct(array $definition, $name = NULL, TypedDataInterface $parent = NULL) { @@ -52,8 +60,10 @@ public function __construct(array $definition, $name = NULL, TypedDataInterface * {@inheritdoc} */ public function getFieldDefinition() { - // @todo https://drupal.org/node/1988612 - return NULL; + if (!isset($this->fieldDefinition)) { + $this->fieldDefinition = new FieldDefinition($this); + } + return $this->fieldDefinition; } /** @@ -255,13 +265,20 @@ protected function getDefaultValue() { /** * {@inheritdoc} */ + public function isRequired() { + return $this->getFieldDefinition()->isFieldRequired(); + } + + /** + * {@inheritdoc} + */ public function getConstraints() { // Constraints usually apply to the field item, but required does make // sense on the field only. So we special-case it to apply to the field for // now. - // @todo: Separate list and list item definitions to separate constraints. + // @todo Separate list and list item definitions to separate constraints. $constraints = array(); - if (!empty($this->definition['required'])) { + if ($this->isRequired()) { $constraints[] = \Drupal::typedData()->getValidationConstraintManager()->create('NotNull', array()); } return $constraints; diff --git a/core/lib/Drupal/Core/Entity/Field/FieldDefinition.php b/core/lib/Drupal/Core/Entity/Field/FieldDefinition.php new file mode 100644 index 0000000..e48a80d --- /dev/null +++ b/core/lib/Drupal/Core/Entity/Field/FieldDefinition.php @@ -0,0 +1,110 @@ +field = $field; + $definition = $field->getDefinition(); + $this->definition = NestedArray::mergeDeep(\Drupal::typedData()->getDefinition($definition['type']), $definition); + } + + /** + * {@inheritdoc} + */ + public function getFieldName() { + return $this->field->getName(); + } + + /** + * {@inheritdoc} + */ + public function getFieldType() { + return $this->definition['field_type']; + } + + /** + * {@inheritdoc} + */ + public function getFieldSettings() { + return $this->definition['settings']; + } + + /** + * {@inheritdoc} + */ + public function getFieldSetting($setting_name) { + return $this->definition['settings'][$setting_name]; + } + + /** + * {@inheritdoc} + */ + public function getFieldPropertyNames() { + return array_keys($this->field->getPropertyDefinitions()); + } + + /** + * {@inheritdoc} + */ + public function isFieldTranslatable() { + return !empty($this->definition['translatable']); + } + + /** + * {@inheritdoc} + */ + public function getFieldLabel() { + return $this->definition['label']; + } + + /** + * {@inheritdoc} + */ + public function getFieldDescription() { + return $this->definition['description']; + } + + /** + * {@inheritdoc} + */ + public function getFieldCardinality() { + return isset($this->definition['cardinality']) ? $this->definition['cardinality'] : 1; + } + + /** + * {@inheritdoc} + */ + public function isFieldRequired() { + return !empty($this->definition['required']); + } + +} diff --git a/core/lib/Drupal/Core/Entity/Field/FieldInterface.php b/core/lib/Drupal/Core/Entity/Field/FieldInterface.php index 0d85da3..fb7ad85 100644 --- a/core/lib/Drupal/Core/Entity/Field/FieldInterface.php +++ b/core/lib/Drupal/Core/Entity/Field/FieldInterface.php @@ -130,4 +130,12 @@ public function delete(); */ public function deleteRevision(); + /** + * Returns TRUE if the field is required. + * + * @return bool + * TRUE if the field is required. + */ + public function isRequired(); + } diff --git a/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/FieldItemDeriver.php b/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/FieldItemDeriver.php index 60b47f9..4c1d1d1 100644 --- a/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/FieldItemDeriver.php +++ b/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/FieldItemDeriver.php @@ -77,6 +77,16 @@ public function getDerivativeDefinition($derivative_id, array $base_plugin_defin */ public function getDerivativeDefinitions(array $base_plugin_definition) { foreach ($this->fieldTypePluginManager->getDefinitions() as $plugin_id => $definition) { + // Provide easy access to the field type without requiring consuming code + // to parse it from the full data type. + $definition['field_type'] = $plugin_id; + + // The distinction between 'settings' and 'instance_settings' is only + // meaningful at the field type plugin level. At the Typed data API level, + // merge them. + $definition['settings'] = $definition['instance_settings'] + $definition['settings']; + unset($definition['instance_settings']); + $this->derivatives[$plugin_id] = $definition; } return $this->derivatives; diff --git a/core/modules/edit/js/editors/directEditor.js b/core/modules/edit/js/editors/directEditor.js index 5fed240..59bb6d8 100644 --- a/core/modules/edit/js/editors/directEditor.js +++ b/core/modules/edit/js/editors/directEditor.js @@ -21,7 +21,13 @@ Drupal.edit.editors.direct = 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 diff --git a/core/modules/edit/lib/Drupal/edit/Access/EditEntityFieldAccessCheck.php b/core/modules/edit/lib/Drupal/edit/Access/EditEntityFieldAccessCheck.php index 9559be7..8e204dd 100644 --- a/core/modules/edit/lib/Drupal/edit/Access/EditEntityFieldAccessCheck.php +++ b/core/modules/edit/lib/Drupal/edit/Access/EditEntityFieldAccessCheck.php @@ -42,7 +42,13 @@ public function access(Route $route, Request $request) { * {@inheritdoc} */ public function accessEditEntityField(EntityInterface $entity, $field_name) { - return $entity->access('update') && ($field = field_info_field($field_name)) && field_access('edit', $field, $entity->entityType(), $entity); + $property_definitions = $entity->getPropertyDefinitions(); + if (isset($property_definitions[$field_name]['configurable']) && $property_definitions[$field_name]['configurable'] === TRUE) { + return $entity->access('update') && ($field = field_info_field($field_name)) && field_access('edit', $field, $entity->entityType(), $entity); + } + else { + return $entity->access('update'); + } } /** @@ -65,13 +71,16 @@ protected function validateAndUpcastRequestAttributes(Request $request) { // Validate the field name and language. $field_name = $request->attributes->get('field_name'); - if (!$field_name || !field_info_instance($entity->entityType(), $field_name, $entity->bundle())) { + if (!$field_name) { throw new NotFoundHttpException(); } $langcode = $request->attributes->get('langcode'); if (!$langcode || (field_valid_language($langcode) !== $langcode)) { throw new NotFoundHttpException(); } + if (!($field_definition = $entity->getTranslation($langcode)->get($field_name)->getFieldDefinition())) { + throw new NotFoundHttpException(); + } } } diff --git a/core/modules/edit/lib/Drupal/edit/EditController.php b/core/modules/edit/lib/Drupal/edit/EditController.php index 2979e89..a3ccb9f 100644 --- a/core/modules/edit/lib/Drupal/edit/EditController.php +++ b/core/modules/edit/lib/Drupal/edit/EditController.php @@ -25,6 +25,7 @@ use Drupal\edit\Ajax\EntitySavedCommand; use Drupal\edit\Ajax\MetadataCommand; use Drupal\user\TempStoreFactory; +use Drupal\field\FieldInstanceInterface; /** * Returns responses for Edit module routes. @@ -132,12 +133,15 @@ 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) { throw new NotFoundHttpException(); } if (!$langcode || (field_valid_language($langcode) !== $langcode)) { throw new NotFoundHttpException(); } + if (!($field_definition = $entity->getTranslation($langcode)->get($field_name)->getFieldDefinition())) { + throw new NotFoundHttpException(); + } // If the entity information for this field is requested, include it. $entity_id = $entity->entityType() . '/' . $entity_id; @@ -145,7 +149,7 @@ public function metadata(Request $request) { $metadata[$entity_id] = $this->metadataGenerator->generateEntity($entity, $langcode); } - $metadata[$field] = $this->metadataGenerator->generateField($entity, $instance, $langcode, $view_mode); + $metadata[$field] = $this->metadataGenerator->generateField($entity, $field_definition, $langcode, $view_mode); } return new JsonResponse($metadata); @@ -216,7 +220,14 @@ public function fieldForm(EntityInterface $entity, $field_name, $langcode, $view $entity = $this->tempStoreFactory->get('edit')->get($entity->uuid()); // @todo Remove when http://drupal.org/node/1346214 is complete. $entity = $entity->getBCEntity(); - $output = field_view_field($entity, $field_name, $view_mode_id, $langcode); + + $field = $entity->getNGEntity()->get($field_name); + if ($field->getFieldDefinition() instanceof FieldInstanceInterface) { + $output = field_view_field($entity, $field_name, $view_mode_id, $langcode); + } + else { + $output = \Drupal::service('plugin.manager.field.formatter')->viewBaseField($field); + } $response->addCommand(new FieldFormSavedCommand(drupal_render($output))); } diff --git a/core/modules/edit/lib/Drupal/edit/Form/EditFieldForm.php b/core/modules/edit/lib/Drupal/edit/Form/EditFieldForm.php index e14e0d2..05312d8 100644 --- a/core/modules/edit/lib/Drupal/edit/Form/EditFieldForm.php +++ b/core/modules/edit/lib/Drupal/edit/Form/EditFieldForm.php @@ -10,6 +10,7 @@ use Drupal; use Drupal\Core\Entity\EntityInterface; use Drupal\user\TempStoreFactory; +use Drupal\field\FieldInstanceInterface; /** * Builds and process a form for editing a single entity field. @@ -33,7 +34,13 @@ public function build(array $form, array &$form_state, EntityInterface $entity, $this->tempStoreFactory = $temp_store_factory; // Add the field form. - field_attach_form($form_state['entity'], $form, $form_state, $form_state['langcode'], array('field_name' => $form_state['field_name'])); + $field = $entity->getNGEntity()->get($field_name); + if ($field->getFieldDefinition() instanceof FieldInstanceInterface) { + field_attach_form($form_state['entity'], $form, $form_state, $form_state['langcode'], array('field_name' => $form_state['field_name'])); + } + else { + $form[$field_name] = \Drupal::service('plugin.manager.field.widget')->baseFieldForm($field, $form, $form_state, $form_state['langcode']); + } // Add a submit button. Give it a class for easy JavaScript targeting. $form['actions'] = array('#type' => 'actions'); @@ -90,7 +97,14 @@ protected function init(array &$form_state, EntityInterface $entity, $field_name */ public function validate(array $form, array &$form_state) { $entity = $this->buildEntity($form, $form_state); - field_attach_form_validate($entity, $form, $form_state, array('field_name' => $form_state['field_name'])); + $field_name = $form_state['field_name']; + $field = $entity->getNGEntity()->get($field_name); + if ($field->getFieldDefinition() instanceof FieldInstanceInterface) { + field_attach_form_validate($entity, $form, $form_state, array('field_name' => $field_name)); + } + else { + // @todo + } } /** @@ -112,13 +126,19 @@ public function submit(array $form, array &$form_state) { protected function buildEntity(array $form, array &$form_state) { $entity = clone $form_state['entity']; - field_attach_extract_form_values($entity, $form, $form_state, array('field_name' => $form_state['field_name'])); + $field_name = $form_state['field_name']; + $field = $entity->getNGEntity()->get($field_name); + if ($field->getFieldDefinition() instanceof FieldInstanceInterface) { + field_attach_extract_form_values($entity, $form, $form_state, array('field_name' => $field_name)); + } + else { + \Drupal::service('plugin.manager.field.widget')->baseFieldExtractFormValues($field, $form, $form_state, $form_state['langcode']); + } // @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['label'])); + $entity->log = t('Updated the %field-name field through in-place editing.', array('%field-name' => $field->getFieldDefinition()->getFieldLabel())); } return $entity; diff --git a/core/modules/edit/lib/Drupal/edit/MetadataGenerator.php b/core/modules/edit/lib/Drupal/edit/MetadataGenerator.php index 74d13f9..279019e 100644 --- a/core/modules/edit/lib/Drupal/edit/MetadataGenerator.php +++ b/core/modules/edit/lib/Drupal/edit/MetadataGenerator.php @@ -77,7 +77,13 @@ public function generateField(EntityInterface $entity, FieldDefinitionInterface } // Early-return if no editor is available. - $formatter_id = entity_get_render_display($entity, $view_mode)->getRenderer($field_name)->getPluginId(); + if ($field_definition instanceof FieldInstanceInterface) { + $formatter_id = entity_get_render_display($entity, $view_mode)->getRenderer($field_name)->getPluginId(); + } + else { + $field_type_info = field_info_field_types($field_definition->getFieldType()); + $formatter_id = $field_type_info['default_formatter']; + } $items = $entity->getTranslation($langcode)->get($field_name)->getValue(); $editor_id = $this->editorSelector->getEditor($formatter_id, $field_definition, $items); if (!isset($editor_id)) { diff --git a/core/modules/field/field.module b/core/modules/field/field.module index c355e67..db840f5 100644 --- a/core/modules/field/field.module +++ b/core/modules/field/field.module @@ -177,6 +177,9 @@ function field_theme() { 'field' => array( 'render element' => 'element', ), + 'field__title' => array( + 'base hook' => 'field', + ), 'field_multiple_value_form' => array( 'render element' => 'element', ), @@ -1004,6 +1007,13 @@ function theme_field($variables) { } /** + * @todo Document. + */ +function theme_field__title($variables) { + return '' . drupal_render($variables['items']) . ''; +} + +/** * Assembles a partial entity structure with initial IDs. * * @param stdClass $ids diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/ConfigField.php b/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/ConfigField.php index 1a58ff4..365cd8c 100644 --- a/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/ConfigField.php +++ b/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/ConfigField.php @@ -24,6 +24,13 @@ class ConfigField extends Field implements ConfigFieldInterface { protected $instance; /** + * @todo Document. + * + * @var array + */ + protected $instances; + + /** * {@inheritdoc} */ public function __construct(array $definition, $name = NULL, TypedDataInterface $parent = NULL) { @@ -37,9 +44,14 @@ public function __construct(array $definition, $name = NULL, TypedDataInterface * {@inheritdoc} */ public function getInstance() { - if (!isset($this->instance) && $parent = $this->getParent()) { - $instances = FieldAPI::fieldInfo()->getBundleInstances($parent->entityType(), $parent->bundle()); - $this->instance = $instances[$this->getName()]; + if (!isset($this->instance)) { + if (!isset($this->instances) && $parent = $this->getParent()) { + $this->instances = FieldAPI::fieldInfo()->getBundleInstances($parent->entityType(), $parent->bundle()); + } + $field_name = $this->getName(); + if (isset($this->instances[$field_name])) { + $this->instance = $this->instances[$field_name]; + } } return $this->instance; } @@ -48,14 +60,22 @@ public function getInstance() { * {@inheritdoc} */ public function getFieldDefinition() { - return $this->getInstance(); + return $this->getInstance() ?: parent::getFieldDefinition(); + } + + /** + * {@inheritdoc} + */ + public function isRequired() { + // Adding a default value is not required even if the field is otherwise. + return empty($this->getParent()->field_ui_default_value) && parent::isRequired(); } /** * {@inheritdoc} */ public function getConstraints() { - $constraints = array(); + $constraints = parent::getConstraints(); // Check that the number of values doesn't exceed the field cardinality. For // form submitted values, this can only happen with 'multiple value' // widgets. diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterBase.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterBase.php index aa93124..f2d9a61 100644 --- a/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterBase.php +++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterBase.php @@ -135,7 +135,7 @@ protected function checkFieldAccess($op, $entity) { return field_access($op, $field, $entity->entityType(), $entity); } else { - return FALSE; + return TRUE; } } diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterPluginManager.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterPluginManager.php index c916a9f..34334be 100644 --- a/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterPluginManager.php +++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterPluginManager.php @@ -17,7 +17,7 @@ use Drupal\Core\Plugin\Discovery\CacheDecorator; use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery; use Drupal\Core\Plugin\Discovery\AlterDecorator; -use Drupal\field\Entity\FieldInstance; +use Drupal\Core\Entity\Field\FieldInterface; /** * Plugin type manager for field formatters. @@ -207,4 +207,28 @@ public function getDefaultSettings($type) { return isset($info['settings']) ? $info['settings'] : array(); } + /** + * @todo Document. + */ + public function viewBaseField(FieldInterface $field) { + $options = array( + 'field_definition' => $field->getFieldDefinition(), + 'view_mode' => 'default', + 'configuration' => array( + 'label' => 'hidden', + ), + ); + $formatter = $this->getInstance($options); + + $entity = $field->getParent()->getBCEntity(); + $entity_id = $entity->id(); + $langcode = $entity->language()->id; + + $items_multi = array($entity_id => $field); + $formatter->prepareView(array($entity_id => $entity), $langcode, $items_multi); + $result = $formatter->view($entity, $langcode, $field); + $field_name = $field->getName(); + return isset($result[$field_name]) ? $result[$field_name] : array(); + } + } diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetBase.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetBase.php index 7c7a343..211ab46 100644 --- a/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetBase.php +++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetBase.php @@ -212,7 +212,7 @@ protected function formMultipleElements(EntityInterface $entity, FieldInterface '#theme' => 'field_multiple_value_form', '#field_name' => $field_name, '#cardinality' => $cardinality, - '#required' => $this->fieldDefinition->isFieldRequired(), + '#required' => $items->isRequired(), '#title' => $title, '#description' => $description, '#prefix' => '