diff --git a/core/config/schema/core.data_types.schema.yml b/core/config/schema/core.data_types.schema.yml
index fb9aad1..9ecef0d 100644
--- a/core/config/schema/core.data_types.schema.yml
+++ b/core/config/schema/core.data_types.schema.yml
@@ -339,3 +339,45 @@ base_entity_reference_field_settings:
target_bundle:
type: string
label: 'Bundle of item to reference'
+
+bundle_field_definition_base:
+ type: config_entity
+ mapping:
+ id:
+ type: string
+ label: 'ID'
+ field_name:
+ type: string
+ label: 'Field name'
+ entity_type:
+ type: string
+ label: 'Entity type'
+ bundle:
+ type: string
+ label: 'Bundle'
+ label:
+ type: label
+ label: 'Label'
+ description:
+ type: text
+ label: 'Help text'
+ required:
+ type: boolean
+ label: 'Required field'
+ translatable:
+ type: boolean
+ label: 'Translatable'
+ default_value:
+ type: field.[%parent.field_type].value
+ default_value_function:
+ type: string
+ label: 'Default value function'
+ settings:
+ type: field.[%parent.field_type].instance_settings
+ field_type:
+ type: string
+ label: 'Field type'
+
+core.base_field_override.*.*.*:
+ type: bundle_field_definition_base
+ label: 'Field override'
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
index fa2c472..66da045 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
@@ -1033,7 +1033,7 @@ protected function getEntityKey($key) {
if (!isset($this->entityKeys[$key]) || !array_key_exists($key, $this->entityKeys)) {
if ($this->getEntityType()->hasKey($key)) {
$field_name = $this->getEntityType()->getKey($key);
- $property = $this->getFieldDefinition($field_name)->getMainPropertyName();
+ $property = $this->getFieldDefinition($field_name)->getFieldStorageDefinition()->getMainPropertyName();
$this->entityKeys[$key] = $this->get($field_name)->$property;
}
else {
diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php
index 3fc38fd..774e16b 100644
--- a/core/lib/Drupal/Core/Entity/EntityManager.php
+++ b/core/lib/Drupal/Core/Entity/EntityManager.php
@@ -11,6 +11,8 @@
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Component\Utility\String;
use Drupal\Core\DependencyInjection\ClassResolverInterface;
+use Drupal\Core\Field\BundleFieldDefinitionInterface;
+use Drupal\Core\Field\Entity\BundleFieldDefinition;
use Drupal\Core\Field\FieldDefinition;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
@@ -22,6 +24,7 @@
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\Core\TypedData\TranslatableInterface;
use Drupal\Core\TypedData\TypedDataManager;
+use Drupal\field\FieldException;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
@@ -465,8 +468,20 @@ protected function buildBundleFieldDefinitions($entity_type_id, $bundle, array $
$entity_type = $this->getDefinition($entity_type_id);
$class = $entity_type->getClass();
- // Allow the entity class to override the base fields.
- $bundle_field_definitions = $class::bundleFieldDefinitions($entity_type, $bundle, $base_field_definitions);
+ $bundle_field_definitions = array();
+ foreach ($base_field_definitions as $field_name => $definition) {
+ // Load any bundle field definitions.
+ /** @var \Drupal\Core\Field\Entity\BundleFieldDefinition $bundle_field_definition */
+ // @todo possible performance optimisation to do a loadMulitple() here
+ // outside of the foreach.
+ $bundle_field_definition = $this->getStorage('bundle_field_definition')->load($entity_type->id() . '.' . $bundle . '.' . $definition->getName());
+ if ($bundle_field_definition) {
+ $bundle_field_definitions[$field_name] = $bundle_field_definition;
+ }
+ }
+ // Allow the entity class to override the base fields. Base fields which
+ // have already been overridden using BundleFieldDefinition take priority.
+ $bundle_field_definitions = array_merge($class::bundleFieldDefinitions($entity_type, $bundle, $base_field_definitions), $bundle_field_definitions);
$provider = $entity_type->getProvider();
foreach ($bundle_field_definitions as $definition) {
// @todo Remove this check once FieldDefinitionInterface exposes a proper
@@ -903,4 +918,18 @@ public function loadEntityByUuid($entity_type_id, $uuid) {
return reset($entities);
}
+ /**
+ * {@inheritdoc}
+ */
+ public function getBundleFieldDefinition($entity_type, $bundle, $field_name) {
+ $definitions = $this->getFieldDefinitions($entity_type, $bundle);
+ if (!isset($definitions[$field_name])) {
+ throw new FieldException(String::format('Field %name or entity type %type does not exist', array('%name' => $field_name, '%type' => $entity_type)));
+ }
+ if ($definitions[$field_name] instanceof BundleFieldDefinitionInterface) {
+ return $definitions[$field_name];
+ }
+ return BundleFieldDefinition::createFromFieldDefinition($definitions[$field_name], $bundle);
+ }
+
}
diff --git a/core/lib/Drupal/Core/Entity/EntityManagerInterface.php b/core/lib/Drupal/Core/Entity/EntityManagerInterface.php
index 104f774..431983a 100644
--- a/core/lib/Drupal/Core/Entity/EntityManagerInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityManagerInterface.php
@@ -374,4 +374,26 @@ public function getFormModeOptions($entity_type_id, $include_disabled = FALSE);
*/
public function loadEntityByUuid($entity_type_id, $uuid);
+ /**
+ * Gets the bundle field definition.
+ *
+ * @param string $entity_type
+ * The entity type ID. Only entity types that implement
+ * \Drupal\Core\Entity\ContentEntityInterface are supported.
+ * @param string $bundle
+ * The bundle to which the definition applies.
+ * @param string $field_name
+ * The name of the field to get the bundle definition of.
+ *
+ * @return \Drupal\Core\Field\BundleFieldDefinitionInterface
+ * If the field definition is a base field it will create a bundle field
+ * definition object. If it is a configurable field instance or the base
+ * field is already overridden it will return that.
+ *
+ * @throws \Drupal\field\FieldException
+ * Exception thrown if called with an non existent field name or entity
+ * type.
+ */
+ public function getBundleFieldDefinition($entity_type, $bundle, $field_name);
+
}
diff --git a/core/lib/Drupal/Core/Field/BundleFieldDefinitionBase.php b/core/lib/Drupal/Core/Field/BundleFieldDefinitionBase.php
new file mode 100644
index 0000000..6ea0f82
--- /dev/null
+++ b/core/lib/Drupal/Core/Field/BundleFieldDefinitionBase.php
@@ -0,0 +1,441 @@
+save()); the default value is
+ * added if the $entity object provides no explicit entry (actual values or
+ * "the field is empty") for the field.
+ *
+ * The default value is expressed as a numerically indexed array of items,
+ * each item being an array of key/value pairs matching the set of 'columns'
+ * defined by the "field schema" for the field type, as exposed in
+ * hook_field_schema(). If the number of items exceeds the cardinality of the
+ * field, extraneous items will be ignored.
+ *
+ * This property is overlooked if the $default_value_function is non-empty.
+ *
+ * Example for a integer field:
+ * @code
+ * array(
+ * array('value' => 1),
+ * array('value' => 2),
+ * )
+ * @endcode
+ *
+ * @var array
+ */
+ public $default_value = array();
+
+ /**
+ * The name of a callback function that returns default values.
+ *
+ * The function will be called with the following arguments:
+ * - \Drupal\Core\Entity\ContentEntityInterface $entity
+ * The entity being created.
+ * - \Drupal\Core\Field\FieldDefinitionInterface $definition
+ * The field definition.
+ * It should return an array of default values, in the same format as the
+ * $default_value property.
+ *
+ * This property takes precedence on the list of fixed values specified in the
+ * $default_value property.
+ *
+ * @var string
+ */
+ public $default_value_function = '';
+
+ /**
+ * The field storage object.
+ *
+ * @var \Drupal\Core\Field\FieldStorageDefinitionInterface
+ */
+ protected $fieldStorage;
+
+ /**
+ * The data definition of a field item.
+ *
+ * @var \Drupal\Core\Field\TypedData\FieldItemDataDefinition
+ */
+ protected $itemDefinition;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function id() {
+ return $this->entity_type . '.' . $this->bundle . '.' . $this->field_name;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName() {
+ return $this->field_name;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getType() {
+ return $this->getFieldStorageDefinition()->getType();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getTargetEntityTypeId() {
+ return $this->entity_type;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function targetBundle() {
+ return $this->bundle;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function toArray() {
+ $properties = parent::toArray();
+ // Additionally, include the field type, that is needed to be able to
+ // generate the field-type-dependant parts of the config schema and to
+ // allow for mapping settings from storage by field type.
+ // @see \Drupal\field\FieldInstanceConfigStorage::mapFromStorageRecords().
+ $properties['field_type'] = $this->getType();
+
+ return $properties;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function calculateDependencies() {
+ parent::calculateDependencies();
+ // Manage dependencies.
+ $bundle_entity_type_id = $this->entityManager()->getDefinition($this->entity_type)->getBundleEntityType();
+ if ($bundle_entity_type_id != 'bundle') {
+ // If the target entity type uses entities to manage its bundles then
+ // depend on the bundle entity.
+ $bundle_entity = $this->entityManager()->getStorage($bundle_entity_type_id)->load($this->bundle);
+ $this->addDependency('entity', $bundle_entity->getConfigDependencyName());
+ }
+ return $this->dependencies;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function postSave(EntityStorageInterface $storage, $update = TRUE) {
+ // Clear the cache.
+ $this->entityManager()->clearCachedFieldDefinitions();
+
+ // Invalidate the render cache for all affected entities.
+ $entity_type = $this->getFieldStorageDefinition()->getTargetEntityTypeId();
+ if ($this->entityManager()->hasController($entity_type, 'view_builder')) {
+ $this->entityManager()->getViewBuilder($entity_type)->resetCache();
+ }
+ }
+
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSettings() {
+ return $this->settings + $this->getFieldStorageDefinition()->getSettings();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSetting($setting_name) {
+ if (array_key_exists($setting_name, $this->settings)) {
+ return $this->settings[$setting_name];
+ }
+ else {
+ return $this->getFieldStorageDefinition()->getSetting($setting_name);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isTranslatable() {
+ // A field can be enabled for translation only if translation is supported.
+ return $this->translatable && $this->getFieldStorageDefinition()->isTranslatable();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setTranslatable($translatable) {
+ $this->translatable = $translatable;
+ return $this;
+ }
+
+ /**
+ * @todo put on FieldDefinitionInterface
+ *
+ * {@inheritdoc}
+ */
+ public function setLabel($label) {
+ $this->label = $label;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getLabel() {
+ return $this->label();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDescription() {
+ return $this->description;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isRequired() {
+ return $this->required;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDefaultValue(ContentEntityInterface $entity) {
+ // Allow custom default values function.
+ if ($function = $this->default_value_function) {
+ $value = call_user_func($function, $entity, $this);
+ }
+ else {
+ $value = $this->default_value;
+ }
+ // Allow the field type to process default values.
+ $field_item_list_class = $this->getClass();
+ return $field_item_list_class::processDefaultValue($value, $entity, $this);
+ }
+
+ /*
+ * Implements the magic __sleep() method.
+ *
+ * Using the Serialize interface and serialize() / unserialize() methods
+ * breaks entity forms in PHP 5.4.
+ * @todo Investigate in https://drupal.org/node/2074253.
+ */
+ public function __sleep() {
+ // Only serialize properties from self::toArray().
+ $properties = array_keys(array_intersect_key($this->toArray(), get_object_vars($this)));
+ // Serialize $entityTypeId property so that toArray() works when waking up.
+ $properties[] = 'entityTypeId';
+ return $properties;
+ }
+
+ /**
+ * Implements the magic __wakeup() method.
+ */
+ public function __wakeup() {
+ // Run the values from self::toArray() through __construct().
+ $values = array_intersect_key($this->toArray(), get_object_vars($this));
+ $this->__construct($values, $this->entityTypeId);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function createFromItemType($item_type) {
+ // Forward to the field definition class for creating new data definitions
+ // via the typed manager.
+ return FieldDefinition::createFromItemType($item_type);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function createFromDataType($type) {
+ // Forward to the field definition class for creating new data definitions
+ // via the typed manager.
+ return FieldDefinition::createFromDataType($type);
+ }
+
+ public function getDataType() {
+ return 'list';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isList() {
+ return TRUE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getClass() {
+ // Derive list class from the field type.
+ $type_definition = \Drupal::service('plugin.manager.field.field_type')
+ ->getDefinition($this->getType());
+ return $type_definition['list_class'];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getConstraints() {
+ return \Drupal::typedDataManager()->getDefaultConstraints($this);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getConstraint($constraint_name) {
+ $constraints = $this->getConstraints();
+ return isset($constraints[$constraint_name]) ? $constraints[$constraint_name] : NULL;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItemDefinition() {
+ if (!isset($this->itemDefinition)) {
+ $this->itemDefinition = FieldItemDataDefinition::create($this)
+ ->setSettings($this->getSettings());
+ }
+ return $this->itemDefinition;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getBundle() {
+ return $this->bundle;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setDefaultValue($value) {
+ if (!is_array($value)) {
+ // Convert to the multi value format to support fields with a cardinality
+ // greater than 1.
+ $value = array(
+ array('value' => $value),
+ );
+ }
+ $this->default_value = $value;
+ return $this;
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Field/BundleFieldDefinitionInterface.php b/core/lib/Drupal/Core/Field/BundleFieldDefinitionInterface.php
new file mode 100644
index 0000000..26da2cd
--- /dev/null
+++ b/core/lib/Drupal/Core/Field/BundleFieldDefinitionInterface.php
@@ -0,0 +1,44 @@
+toArray();
+ $values['bundle'] = $bundle;
+ $values['baseFieldDefinition'] = $field_definition;
+ return \Drupal::entityManager()->getStorage('bundle_field_definition')->create($values);
+ }
+
+ /**
+ * Constructs a BundleFieldDefinition object.
+ *
+ * In most cases, bundle field definition entities are created via
+ * BundleFieldDefinition::createFromFieldDefinition($definition, 'bundle')
+ *
+ * @param array $values
+ * An array of bundle field definition properties, keyed by property name.
+ * The field this is an instance of can be specified either with:
+ * - field: the FieldStorageDefinitionInterface object,
+ * or by referring to an existing field with:
+ * - field_name: The field name.
+ * - entity_type: The entity type.
+ * Additionally, a 'bundle' property is required to indicate the entity
+ * bundle to which the bundle field definition is attached to. Other array
+ * elements will be used to set the corresponding properties on the class;
+ * see the class property documentation for details.
+ * @param string $entity_type
+ * (optional) The type of the entity to create. Defaults to
+ * 'bundle_field_definition'.
+ *
+ * @see entity_create()
+ */
+ public function __construct(array $values, $entity_type = 'bundle_field_definition') {
+ // Allow either an injected FieldDefinition object, or a field_name and
+ // entity_type.
+ if (isset($values['field'])) {
+ if (!$values['field'] instanceof FieldDefinitionInterface && !$values['field'] instanceof FieldStorageDefinitionInterface) {
+ throw new FieldException('Attempt to create a field override on a configurable field.');
+ }
+ $this->fieldStorage = $values['field'];
+ $values['field_name'] = $this->fieldStorage->getName();
+ $values['entity_type'] = $this->fieldStorage->getTargetEntityTypeId();
+ }
+ else {
+ if (empty($values['field_name'])) {
+ throw new FieldException('Attempt to create a bundle field definition of a field without a field_name.');
+ }
+ if (empty($values['entity_type'])) {
+ throw new FieldException(String::format('Attempt to create an bundle field definition of field @field_name without an entity_type.', array('@field_name' => $values['field_name'])));
+ }
+ }
+ // 'bundle' is required in either case.
+ if (empty($values['bundle'])) {
+ throw new FieldException(String::format('Attempt to create an bundle field definition of field @field_name without a bundle.', array('@field_name' => $values['field_name'])));
+ }
+
+ // Discard the 'field_type' entry that is added in config records to ease
+ // schema generation. See self::toArray().
+ unset($values['field_type']);
+
+ parent::__construct($values, $entity_type);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFieldStorageDefinition() {
+ return $this->getBaseFieldDefinition()->getFieldStorageDefinition();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isDisplayConfigurable($context) {
+ return $this->getBaseFieldDefinition()->isDisplayConfigurable($context);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDisplayOptions($display_context) {
+ return $this->getBaseFieldDefinition()->getDisplayOptions($display_context);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isReadOnly() {
+ return $this->getBaseFieldDefinition()->isReadOnly();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isComputed() {
+ return $this->getBaseFieldDefinition()->isComputed();
+ }
+
+ /**
+ * Gets the base field definition.
+ *
+ * @return \Drupal\Core\Field\FieldDefinition
+ */
+ protected function getBaseFieldDefinition() {
+ if (!isset($this->baseFieldDefinition)) {
+ $fields = $this->entityManager()->getBaseFieldDefinitions($this->entity_type);
+ $this->baseFieldDefinition = $fields[$this->getName()];
+ }
+ return $this->baseFieldDefinition;
+ }
+
+ /**
+ * Loads a bundle field definition config entity.
+ *
+ * @param string $entity_type_id
+ * ID of the entity type.
+ * @param string $bundle
+ * Bundle name.
+ * @param string $field_name
+ * Name of the field.
+ *
+ * @return static
+ * The bundle field definition config entity if one exists for the provided
+ * field name, otherwise NULL.
+ */
+ public static function loadByName($entity_type_id, $bundle, $field_name) {
+ return \Drupal::entityManager()->getStorage('bundle_field_definition')->load($entity_type_id . '.' . $bundle . '.' . $field_name);
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Field/FieldDefinitionInterface.php b/core/lib/Drupal/Core/Field/FieldDefinitionInterface.php
index cf6947b..d29f012 100644
--- a/core/lib/Drupal/Core/Field/FieldDefinitionInterface.php
+++ b/core/lib/Drupal/Core/Field/FieldDefinitionInterface.php
@@ -170,20 +170,25 @@ public function getDefaultValue(ContentEntityInterface $entity);
public function isTranslatable();
/**
- * Sets whether the field is translatable.
- *
- * @param bool $translatable
- * Whether the field is translatable.
- *
- * @return $this
- */
- public function setTranslatable($translatable);
-
- /**
* Returns the field storage definition.
*
* @return \Drupal\Core\Field\FieldStorageDefinitionInterface
* The field storage definition.
*/
public function getFieldStorageDefinition();
+
+ /**
+ * Sets a default value.
+ *
+ * Note that if a default value callback is set, it will take precedence over
+ * any value set here.
+ *
+ * @param mixed $value
+ * The default value in the format as returned by
+ * \Drupal\Core\Field\FieldDefinitionInterface::getDefaultValue().
+ *
+ * @return $this
+ */
+ public function setDefaultValue($value);
+
}
diff --git a/core/modules/book/config/install/node.type.book.yml b/core/modules/book/config/install/node.type.book.yml
index d2409d4..79deb64 100644
--- a/core/modules/book/config/install/node.type.book.yml
+++ b/core/modules/book/config/install/node.type.book.yml
@@ -2,7 +2,6 @@ type: book
name: 'Book page'
description: 'Books have a built-in hierarchical navigation. Use for handbooks or tutorials.'
help: ''
-title_label: Title
settings:
node:
preview: 1
diff --git a/core/modules/content_translation/content_translation.admin.inc b/core/modules/content_translation/content_translation.admin.inc
index 7e7e790..e6219d7 100644
--- a/core/modules/content_translation/content_translation.admin.inc
+++ b/core/modules/content_translation/content_translation.admin.inc
@@ -98,7 +98,6 @@ function _content_translation_form_language_content_settings_form_alter(array &$
'#default_value' => content_translation_enabled($entity_type_id, $bundle),
);
- $field_settings = content_translation_get_config($entity_type_id, $bundle, 'fields');
foreach ($fields as $field_name => $definition) {
// Allow to configure only fields supporting multilingual storage.
if (!empty($storage_definitions[$field_name]) && $storage_definitions[$field_name]->isTranslatable()) {
@@ -297,7 +296,6 @@ function content_translation_form_language_content_settings_submit(array $form,
$bundle_settings['translatable'] = $bundle_settings['translatable'] && $entity_types[$entity_type_id];
}
if (!empty($bundle_settings['fields'])) {
- $definitions = \Drupal::entityManager()->getFieldDefinitions($entity_type_id, $bundle);
foreach ($bundle_settings['fields'] as $field_name => $translatable) {
$translatable = $translatable && $bundle_settings['translatable'];
// If we have column settings and no column is translatable, no point
@@ -305,20 +303,11 @@ function content_translation_form_language_content_settings_submit(array $form,
if (isset($bundle_settings['columns'][$field_name]) && !array_filter($bundle_settings['columns'][$field_name])) {
$translatable = FALSE;
}
- // If we have a storable field definition we directly persist any
- // change to translatability, otherwise we store changes in our config
- // so we can properly alter field definitions later.
- // @todo Remove this special casing when any field definition can have
- // a configurable bundle override. See
- // https://drupal.org/node/2224761.
- $definition = $definitions[$field_name];
- if ($definition instanceof FieldInstanceConfigInterface) {
- if ($definition->isTranslatable() != $translatable) {
- $definition->setTranslatable($translatable);
- $definition->save();
- }
- // Do not save configurable fields unnecessarily.
- unset($bundle_settings['fields'][$field_name]);
+ $definition = \Drupal::entityManager()->getBundleFieldDefinition($entity_type_id, $bundle, $field_name);
+ if ($definition->isTranslatable() != $translatable) {
+ $definition
+ ->setTranslatable($translatable)
+ ->save();
}
}
}
diff --git a/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module
index 4fbd597..9a3cb00 100644
--- a/core/modules/content_translation/content_translation.module
+++ b/core/modules/content_translation/content_translation.module
@@ -8,11 +8,9 @@
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityFormInterface;
use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\field\Entity\FieldInstanceConfig;
-use Drupal\field\FieldInstanceConfigInterface;
use Drupal\node\NodeInterface;
/**
@@ -142,34 +140,6 @@ function content_translation_entity_bundle_info_alter(&$bundles) {
}
/**
- * Implements hook_entity_bundle_field_info_alter().
- *
- * @todo Remove this when any field supports per-bundle configurable overrides.
- * See https://drupal.org/node/2224761.
- */
-function content_translation_entity_bundle_field_info_alter(&$fields, EntityTypeInterface $entity_type, $bundle) {
- $settings = content_translation_get_config($entity_type->id(), $bundle, 'fields');
- if (!empty($settings)) {
- $base_field_definitions = \Drupal::entityManager()->getBaseFieldDefinitions($entity_type->id());
- foreach ($settings as $name => $translatable) {
- // Skip storable field definitions as those are supposed to have already
- // been changed.
- if (!isset($fields[$name]) || !$fields[$name] instanceof FieldInstanceConfigInterface) {
- // If we do not have a bundle definition for the current field and we
- // have to override its translatability, we need to instantiate a new
- // bundle field definition from the base one.
- if (!isset($fields[$name]) && isset($base_field_definitions[$name]) && $base_field_definitions[$name]->isTranslatable() != $translatable) {
- $fields[$name] = clone $base_field_definitions[$name];
- }
- if (isset($fields[$name])) {
- $fields[$name]->setTranslatable((bool) $translatable);
- }
- }
- }
- }
-}
-
-/**
* Implements hook_field_info_alter().
*
* Content translation extends the @FieldType annotation with following key:
@@ -843,11 +813,6 @@ function content_translation_save_settings($settings) {
// Store whether a bundle has translation enabled or not.
content_translation_set_config($entity_type, $bundle, 'enabled', $bundle_settings['translatable']);
- // Store whether fields are translatable or not.
- if (!empty($bundle_settings['fields'])) {
- content_translation_set_config($entity_type, $bundle, 'fields', $bundle_settings['fields']);
- }
-
// Store whether fields have translation enabled or not.
if (!empty($bundle_settings['columns'])) {
foreach ($bundle_settings['columns'] as $field_name => $column_settings) {
diff --git a/core/modules/content_translation/src/Tests/ContentTranslationSettingsTest.php b/core/modules/content_translation/src/Tests/ContentTranslationSettingsTest.php
index 445fd5b..c93eb22 100644
--- a/core/modules/content_translation/src/Tests/ContentTranslationSettingsTest.php
+++ b/core/modules/content_translation/src/Tests/ContentTranslationSettingsTest.php
@@ -8,6 +8,7 @@
namespace Drupal\content_translation\Tests;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
+use Drupal\Core\Field\Entity\BundleFieldDefinition;
use Drupal\Core\Language\Language;
use Drupal\field\Entity\FieldInstanceConfig;
use Drupal\simpletest\WebTestBase;
@@ -113,10 +114,6 @@ function testSettingsUI() {
$definition = $this->entityManager()->getFieldDefinitions('comment', 'comment')['subject'];
$this->assertFalse($definition->isTranslatable(), 'Page comment subject is not translatable.');
- $settings = content_translation_get_config('comment', 'comment_article', 'fields');
- $this->assertFalse(isset($settings['comment_body']), 'Configurable fields are not saved to content_translation.settings.');
- $this->assertTrue(isset($settings['subject']), 'Base fields are saved to content_translation.settings.');
-
// Test that translation can be enabled for base fields.
$edit = array(
'entity_types[entity_test_mul]' => TRUE,
@@ -125,8 +122,8 @@ function testSettingsUI() {
'settings[entity_test_mul][entity_test_mul][fields][user_id]' => FALSE,
);
$this->assertSettings('entity_test_mul', 'entity_test_mul', TRUE, $edit);
- $settings = content_translation_get_config('entity_test_mul', 'entity_test_mul', 'fields');
- $this->assertTrue($settings['name'] && !$settings['user_id'], 'Base fields are saved to content_translation.settings.');
+ $bundle_definition = BundleFieldDefinition::loadByName('entity_test_mul', 'entity_test_mul', 'name');
+ $this->assertTrue($bundle_definition->isTranslatable(), 'Base fields can overridden with a bundle field definition entity.');
$definitions = $this->entityManager()->getFieldDefinitions('entity_test_mul', 'entity_test_mul');
$this->assertTrue($definitions['name']->isTranslatable() && !$definitions['user_id']->isTranslatable(), 'Bundle field definitions were correctly altered.');
diff --git a/core/modules/field/config/schema/field.schema.yml b/core/modules/field/config/schema/field.schema.yml
index ce97e19..fbd0344 100644
--- a/core/modules/field/config/schema/field.schema.yml
+++ b/core/modules/field/config/schema/field.schema.yml
@@ -49,43 +49,8 @@ field.storage.*.*:
label: 'Index'
field.instance.*.*.*:
- type: config_entity
+ type: bundle_field_definition_base
label: 'Field instance'
- mapping:
- id:
- type: string
- label: 'ID'
- label:
- type: label
- label: 'Label'
- field_name:
- type: string
- label: 'Field name'
- entity_type:
- type: string
- label: 'Entity type'
- bundle:
- type: string
- label: 'Bundle'
- description:
- type: text
- label: 'Help text'
- required:
- type: boolean
- label: 'Required field'
- translatable:
- type: boolean
- label: 'Translatable'
- default_value:
- type: field.[%parent.field_type].value
- default_value_function:
- type: string
- label: 'Default value function'
- settings:
- type: field.[%parent.field_type].instance_settings
- field_type:
- type: string
- label: 'Field type'
entity_form_display.field.hidden:
type: entity_field_form_display_base
@@ -97,6 +62,26 @@ entity_form_display.field.hidden:
sequence:
- type: string
+# Schema for the String field type.
+
+field.string.instance_settings:
+ type: sequence
+ label: 'String settings'
+ sequence:
+ - type: string
+ label: 'Setting'
+
+field.string.value:
+ type: sequence
+ label: 'Default value'
+ sequence:
+ - type: mapping
+ label: 'Default'
+ mapping:
+ value:
+ type: string
+ label: 'Value'
+
# Schema for the configuration files of the Boolean field type.
field.boolean.settings:
type: mapping
diff --git a/core/modules/field/src/Entity/FieldInstanceConfig.php b/core/modules/field/src/Entity/FieldInstanceConfig.php
index 8ce7afa..aa0fa39 100644
--- a/core/modules/field/src/Entity/FieldInstanceConfig.php
+++ b/core/modules/field/src/Entity/FieldInstanceConfig.php
@@ -8,11 +8,8 @@
namespace Drupal\field\Entity;
use Drupal\Component\Utility\String;
-use Drupal\Core\Config\Entity\ConfigEntityBase;
-use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityStorageInterface;
-use Drupal\Core\Field\FieldDefinition;
-use Drupal\Core\Field\TypedData\FieldItemDataDefinition;
+use Drupal\Core\Field\BundleFieldDefinitionBase;
use Drupal\field\FieldException;
use Drupal\field\FieldStorageConfigInterface;
use Drupal\field\FieldInstanceConfigInterface;
@@ -34,141 +31,7 @@
* }
* )
*/
-class FieldInstanceConfig extends ConfigEntityBase implements FieldInstanceConfigInterface {
-
- /**
- * The instance ID.
- *
- * The ID consists of 3 parts: the entity type, bundle and the field name.
- *
- * Example: node.article.body, user.user.field_main_image.
- *
- * @var string
- */
- public $id;
-
- /**
- * The name of the field attached to the bundle by this instance.
- *
- * @var string
- */
- public $field_name;
-
- /**
- * The name of the entity type the instance is attached to.
- *
- * @var string
- */
- public $entity_type;
-
- /**
- * The name of the bundle the instance is attached to.
- *
- * @var string
- */
- public $bundle;
-
- /**
- * The human-readable label for the instance.
- *
- * This will be used as the title of Form API elements for the field in entity
- * edit forms, or as the label for the field values in displayed entities.
- *
- * If not specified, this defaults to the field_name (mostly useful for field
- * instances created in tests).
- *
- * @var string
- */
- public $label;
-
- /**
- * The instance description.
- *
- * A human-readable description for the field when used with this bundle.
- * For example, the description will be the help text of Form API elements for
- * this instance in entity edit forms.
- *
- * @var string
- */
- public $description = '';
-
- /**
- * Field-type specific settings.
- *
- * An array of key/value pairs. The keys and default values are defined by the
- * field type.
- *
- * @var array
- */
- public $settings = array();
-
- /**
- * Flag indicating whether the field is required.
- *
- * TRUE if a value for this field is required when used with this bundle,
- * FALSE otherwise. Currently, required-ness is only enforced at the Form API
- * level in entity edit forms, not during direct API saves.
- *
- * @var bool
- */
- public $required = FALSE;
-
- /**
- * Flag indicating whether the field is translatable.
- *
- * Defaults to TRUE.
- *
- * @var bool
- */
- public $translatable = TRUE;
-
- /**
- * Default field value.
- *
- * The default value is used when an entity is created, either:
- * - through an entity creation form; the form elements for the field are
- * prepopulated with the default value.
- * - through direct API calls (i.e. $entity->save()); the default value is
- * added if the $entity object provides no explicit entry (actual values or
- * "the field is empty") for the field.
- *
- * The default value is expressed as a numerically indexed array of items,
- * each item being an array of key/value pairs matching the set of 'columns'
- * defined by the "field schema" for the field type, as exposed in
- * hook_field_schema(). If the number of items exceeds the cardinality of the
- * field, extraneous items will be ignored.
- *
- * This property is overlooked if the $default_value_function is non-empty.
- *
- * Example for a integer field:
- * @code
- * array(
- * array('value' => 1),
- * array('value' => 2),
- * )
- * @endcode
- *
- * @var array
- */
- public $default_value = array();
-
- /**
- * The name of a callback function that returns default values.
- *
- * The function will be called with the following arguments:
- * - \Drupal\Core\Entity\ContentEntityInterface $entity
- * The entity being created.
- * - \Drupal\Core\Field\FieldDefinitionInterface $definition
- * The field definition.
- * It should return an array of default values, in the same format as the
- * $default_value property.
- *
- * This property takes precedence on the list of fixed values specified in the
- * $default_value property.
- *
- * @var string
- */
- public $default_value_function = '';
+class FieldInstanceConfig extends BundleFieldDefinitionBase implements FieldInstanceConfigInterface {
/**
* Flag indicating whether the instance is deleted.
@@ -200,13 +63,6 @@ class FieldInstanceConfig extends ConfigEntityBase implements FieldInstanceConfi
protected $bundle_rename_allowed = FALSE;
/**
- * The data definition of a field item.
- *
- * @var \Drupal\Core\Field\TypedData\FieldItemDataDefinition
- */
- protected $itemDefinition;
-
- /**
* Constructs a FieldInstanceConfig object.
*
* In most cases, Field instance entities are created via
@@ -255,7 +111,7 @@ public function __construct(array $values, $entity_type = 'field_instance_config
// Discard the 'field_type' entry that is added in config records to ease
// schema generation and mapping settings from storage.
- // @see Drupal\field\Entity\FieldInstanceConfig::toArray().
+ // @see \Drupal\Core\Field\BundleFieldDefinitionBase::toArray().
unset($values['field_type']);
parent::__construct($values, $entity_type);
@@ -264,42 +120,6 @@ public function __construct(array $values, $entity_type = 'field_instance_config
/**
* {@inheritdoc}
*/
- public function id() {
- return $this->entity_type . '.' . $this->bundle . '.' . $this->field_name;
- }
-
- /**
- * {@inheritdoc}
- */
- public function getName() {
- return $this->field_name;
- }
-
- /**
- * {@inheritdoc}
- */
- public function getType() {
- return $this->getFieldStorageDefinition()->getType();
- }
-
-
- /**
- * {@inheritdoc}
- */
- public function toArray() {
- $properties = parent::toArray();
- // Additionally, include the field type, that is needed to be able to
- // generate the field-type-dependant parts of the config schema and to
- // allow for mapping settings from storage by field type.
- // @see \Drupal\field\FieldInstanceConfigStorage::mapFromStorageRecords().
- $properties['field_type'] = $this->getType();
-
- return $properties;
- }
-
- /**
- * {@inheritdoc}
- */
public function postCreate(EntityStorageInterface $storage) {
// Validate that we have a valid storage for this instance. This throws an
// exception if the storage is invalid.
@@ -361,34 +181,12 @@ public function calculateDependencies() {
parent::calculateDependencies();
// Manage dependencies.
$this->addDependency('entity', $this->getFieldStorageDefinition()->getConfigDependencyName());
- $bundle_entity_type_id = \Drupal::entityManager()->getDefinition($this->entity_type)->getBundleEntityType();
- if ($bundle_entity_type_id != 'bundle') {
- // If the target entity type uses entities to manage its bundles then
- // depend on the bundle entity.
- $bundle_entity = \Drupal::entityManager()->getStorage($bundle_entity_type_id)->load($this->bundle);
- $this->addDependency('entity', $bundle_entity->getConfigDependencyName());
- }
return $this->dependencies;
}
/**
* {@inheritdoc}
*/
- public function postSave(EntityStorageInterface $storage, $update = TRUE) {
- // Clear the cache.
- \Drupal::entityManager()->clearCachedFieldDefinitions();
-
- // Invalidate the render cache for all affected entities.
- $entity_manager = \Drupal::entityManager();
- $entity_type = $this->getFieldStorageDefinition()->getTargetEntityTypeId();
- if ($entity_manager->hasController($entity_type, 'view_builder')) {
- $entity_manager->getViewBuilder($entity_type)->resetCache();
- }
- }
-
- /**
- * {@inheritdoc}
- */
public static function preDelete(EntityStorageInterface $storage, array $instances) {
$state = \Drupal::state();
@@ -467,59 +265,6 @@ public static function postDelete(EntityStorageInterface $storage, array $instan
/**
* {@inheritdoc}
*/
- public function getFieldStorageDefinition() {
- if (!$this->fieldStorage) {
- $storages = \Drupal::entityManager()->getFieldStorageDefinitions($this->entity_type);
- if (!isset($storages[$this->field_name])) {
- throw new FieldException(String::format('Attempt to create an instance @field_name while the storage does not exist on entity type @entity_type.', array('@field_name' => $this->field_name, '@entity_type' => $this->entity_type)));
- }
- if (!$storages[$this->field_name] instanceof FieldStorageConfigInterface) {
- throw new FieldException(String::format('Attempt to create a configurable instance for a non-configurable storage @field_name.', array('@field_name' => $this->field_name, '@entity_type' => $this->entity_type)));
- }
- $this->fieldStorage = $storages[$this->field_name];
- }
-
- return $this->fieldStorage;
- }
-
- /**
- * {@inheritdoc}
- */
- public function getSettings() {
- return $this->settings + $this->getFieldStorageDefinition()->getSettings();
- }
-
- /**
- * {@inheritdoc}
- */
- public function getSetting($setting_name) {
- if (array_key_exists($setting_name, $this->settings)) {
- return $this->settings[$setting_name];
- }
- else {
- return $this->getFieldStorageDefinition()->getSetting($setting_name);
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function isTranslatable() {
- // A field can be enabled for translation only if translation is supported.
- return $this->translatable && $this->getFieldStorageDefinition()->isTranslatable();
- }
-
- /**
- * {@inheritdoc}
- */
- public function setTranslatable($translatable) {
- $this->translatable = $translatable;
- return $this;
- }
-
- /**
- * {@inheritdoc}
- */
protected function linkTemplates() {
$link_templates = parent::linkTemplates();
if (\Drupal::moduleHandler()->moduleExists('field_ui')) {
@@ -547,38 +292,33 @@ protected function urlRouteParameters($rel) {
/**
* {@inheritdoc}
*/
- public function getLabel() {
- return $this->label();
- }
-
- /**
- * {@inheritdoc}
- */
- public function getDescription() {
- return $this->description;
+ public function allowBundleRename() {
+ $this->bundle_rename_allowed = TRUE;
}
/**
* {@inheritdoc}
*/
- public function isRequired() {
- return $this->required;
+ public function isDeleted() {
+ return $this->deleted;
}
/**
* {@inheritdoc}
*/
- public function getDefaultValue(ContentEntityInterface $entity) {
- // Allow custom default values function.
- if ($function = $this->default_value_function) {
- $value = call_user_func($function, $entity, $this);
- }
- else {
- $value = $this->default_value;
+ public function getFieldStorageDefinition() {
+ if (!$this->fieldStorage) {
+ $fields = $this->entityManager()->getFieldStorageDefinitions($this->entity_type);
+ if (!isset($fields[$this->field_name])) {
+ throw new FieldException(String::format('Attempt to create an instance of field @field_name that does not exist on entity type @entity_type.', array('@field_name' => $this->field_name, '@entity_type' => $this->entity_type)));
+ }
+ if (!$fields[$this->field_name] instanceof FieldStorageConfigInterface) {
+ throw new FieldException(String::format('Attempt to create a configurable instance of non-configurable field @field_name.', array('@field_name' => $this->field_name, '@entity_type' => $this->entity_type)));
+ }
+ $this->fieldStorage = $fields[$this->field_name];
}
- // Allow the field type to process default values.
- $field_item_list_class = $this->getClass();
- return $field_item_list_class::processDefaultValue($value, $entity, $this);
+
+ return $this->fieldStorage;
}
/**
@@ -599,80 +339,6 @@ public function getDisplayOptions($display_context) {
/**
* {@inheritdoc}
*/
- public function getBundle() {
- return $this->bundle;
- }
-
- /**
- * {@inheritdoc}
- */
- public function allowBundleRename() {
- $this->bundle_rename_allowed = TRUE;
- }
-
- /**
- * {@inheritdoc}
- */
- public function targetBundle() {
- return $this->bundle;
- }
-
- /*
- * Implements the magic __sleep() method.
- *
- * Using the Serialize interface and serialize() / unserialize() methods
- * breaks entity forms in PHP 5.4.
- * @todo Investigate in https://drupal.org/node/2074253.
- */
- public function __sleep() {
- // Only serialize properties from self::toArray().
- $properties = array_keys(array_intersect_key($this->toArray(), get_object_vars($this)));
- // Serialize $entityTypeId property so that toArray() works when waking up.
- $properties[] = 'entityTypeId';
- return $properties;
- }
-
- /**
- * Implements the magic __wakeup() method.
- */
- public function __wakeup() {
- // Run the values from self::toArray() through __construct().
- $values = array_intersect_key($this->toArray(), get_object_vars($this));
- $this->__construct($values);
- }
-
- /**
- * {@inheritdoc}
- */
- public static function createFromDataType($type) {
- // Forward to the field definition class for creating new data definitions
- // via the typed manager.
- return FieldDefinition::createFromDataType($type);
- }
-
- /**
- * {@inheritdoc}
- */
- public static function createFromItemType($item_type) {
- // Forward to the field definition class for creating new data definitions
- // via the typed manager.
- return FieldDefinition::createFromItemType($item_type);
- }
-
- public function getDataType() {
- return 'list';
- }
-
- /**
- * {@inheritdoc}
- */
- public function isList() {
- return TRUE;
- }
-
- /**
- * {@inheritdoc}
- */
public function isReadOnly() {
return FALSE;
}
@@ -685,49 +351,6 @@ public function isComputed() {
}
/**
- * {@inheritdoc}
- */
- public function getClass() {
- // Derive list class from the field type.
- $type_definition = \Drupal::service('plugin.manager.field.field_type')
- ->getDefinition($this->getType());
- return $type_definition['list_class'];
- }
-
- /**
- * {@inheritdoc}
- */
- public function getConstraints() {
- return \Drupal::typedDataManager()->getDefaultConstraints($this);
- }
-
- /**
- * {@inheritdoc}
- */
- public function getConstraint($constraint_name) {
- $constraints = $this->getConstraints();
- return isset($constraints[$constraint_name]) ? $constraints[$constraint_name] : NULL;
- }
-
- /**
- * {@inheritdoc}
- */
- public function getItemDefinition() {
- if (!isset($this->itemDefinition)) {
- $this->itemDefinition = FieldItemDataDefinition::create($this)
- ->setSettings($this->getSettings());
- }
- return $this->itemDefinition;
- }
-
- /**
- * {@inheritdoc}
- */
- public function isDeleted() {
- return $this->deleted;
- }
-
- /**
* Loads a field config entity based on the entity type and field name.
*
* @param string $entity_type_id
diff --git a/core/modules/forum/config/install/core.base_field_override.node.forum.title.yml b/core/modules/forum/config/install/core.base_field_override.node.forum.title.yml
new file mode 100644
index 0000000..e22a145
--- /dev/null
+++ b/core/modules/forum/config/install/core.base_field_override.node.forum.title.yml
@@ -0,0 +1,17 @@
+langcode: en
+status: true
+dependencies:
+ entity:
+ - node.type.forum
+id: node.forum.title
+field_name: title
+entity_type: node
+bundle: forum
+label: Subject
+description: 'The title of this node, always treated as non-markup plain text.'
+required: true
+translatable: true
+default_value: { }
+default_value_function: ''
+settings: { }
+field_type: string
diff --git a/core/modules/forum/config/install/node.type.forum.yml b/core/modules/forum/config/install/node.type.forum.yml
index f2e86af..d2ecf03 100644
--- a/core/modules/forum/config/install/node.type.forum.yml
+++ b/core/modules/forum/config/install/node.type.forum.yml
@@ -2,7 +2,6 @@ type: forum
name: 'Forum topic'
description: 'A forum topic starts a new discussion thread within a forum.'
help: ''
-title_label: Subject
settings:
node:
preview: 1
diff --git a/core/modules/forum/src/Tests/ForumTest.php b/core/modules/forum/src/Tests/ForumTest.php
index e1d6749..f9470e5 100644
--- a/core/modules/forum/src/Tests/ForumTest.php
+++ b/core/modules/forum/src/Tests/ForumTest.php
@@ -175,7 +175,7 @@ function testForum() {
// Test loading multiple forum nodes on the front page.
$this->drupalLogin($this->drupalCreateUser(array('administer content types', 'create forum content', 'post comments')));
- $this->drupalPostForm('admin/structure/types/manage/forum', array('settings[node][options][promote]' => 'promote'), t('Save content type'));
+ $this->drupalPostForm('admin/structure/types/manage/forum', array('options[promote]' => 'promote'), t('Save content type'));
$this->createForumTopic($this->forum, FALSE);
$this->createForumTopic($this->forum, FALSE);
$this->drupalGet('node');
diff --git a/core/modules/migrate_drupal/src/Tests/d6/MigrateNodeTypeTest.php b/core/modules/migrate_drupal/src/Tests/d6/MigrateNodeTypeTest.php
index 094ca3d..2803678 100644
--- a/core/modules/migrate_drupal/src/Tests/d6/MigrateNodeTypeTest.php
+++ b/core/modules/migrate_drupal/src/Tests/d6/MigrateNodeTypeTest.php
@@ -49,9 +49,6 @@ public function testNodeType() {
$this->assertEqual($node_type_page->id(), 'test_page', 'Node type test_page loaded');
$expected = array(
'options' => array(
- 'status' => TRUE,
- 'promote' => TRUE,
- 'sticky' => FALSE,
'revision' => FALSE,
),
'preview' => 1,
@@ -70,9 +67,6 @@ public function testNodeType() {
$this->assertEqual($node_type_story->id(), 'test_story', 'Node type test_story loaded');
$expected = array(
'options' => array(
- 'status' => TRUE,
- 'promote' => TRUE,
- 'sticky' => FALSE,
'revision' => FALSE,
),
'preview' => 1,
diff --git a/core/modules/node/config/schema/node.schema.yml b/core/modules/node/config/schema/node.schema.yml
index 81e0977..34d2086 100644
--- a/core/modules/node/config/schema/node.schema.yml
+++ b/core/modules/node/config/schema/node.schema.yml
@@ -27,9 +27,6 @@ node.type.*:
help:
type: text
label: 'Explanation or submission guidelines'
- title_label:
- type: label
- label: 'Title field label'
settings:
type: mapping
label: 'Settings'
diff --git a/core/modules/node/content_types.js b/core/modules/node/content_types.js
index cf0df4b..7b8b9a2 100644
--- a/core/modules/node/content_types.js
+++ b/core/modules/node/content_types.js
@@ -18,10 +18,10 @@
});
$context.find('#edit-workflow').drupalSetSummary(function (context) {
var vals = [];
- $(context).find("input[name^='settings[node][options']:checked").parent().each(function () {
+ $(context).find("input[name^='options']:checked").parent().each(function () {
vals.push(Drupal.checkPlain($(this).text()));
});
- if (!$(context).find('#edit-settings-node-options-status').is(':checked')) {
+ if (!$(context).find('#edit-options-status').is(':checked')) {
vals.unshift(Drupal.t('Not published'));
}
return vals.join(', ');
diff --git a/core/modules/node/src/Entity/Node.php b/core/modules/node/src/Entity/Node.php
index 19f311e..b35cc73 100644
--- a/core/modules/node/src/Entity/Node.php
+++ b/core/modules/node/src/Entity/Node.php
@@ -380,7 +380,8 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
->setLabel(t('Publishing status'))
->setDescription(t('A boolean indicating whether the node is published.'))
->setRevisionable(TRUE)
- ->setTranslatable(TRUE);
+ ->setTranslatable(TRUE)
+ ->setDefaultValue(TRUE);
$fields['created'] = FieldDefinition::create('created')
->setLabel(t('Created'))
@@ -398,7 +399,8 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
->setLabel(t('Promote'))
->setDescription(t('A boolean indicating whether the node should be displayed on the front page.'))
->setRevisionable(TRUE)
- ->setTranslatable(TRUE);
+ ->setTranslatable(TRUE)
+ ->setDefaultValue(TRUE);
$fields['sticky'] = FieldDefinition::create('boolean')
->setLabel(t('Sticky'))
@@ -428,37 +430,4 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
return $fields;
}
- /**
- * {@inheritdoc}
- */
- public static function bundleFieldDefinitions(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
- $node_type = node_type_load($bundle);
- $fields = array();
-
- // When deleting a node type the corresponding node displays are deleted as
- // well. In order to be deleted, they need to be loaded first. Entity
- // displays, however, fetch the field definitions of the respective entity
- // type to fill in their defaults. Therefore this function ends up being
- // called with a non-existing bundle.
- // @todo Fix this in https://drupal.org/node/2248795
- if (!$node_type) {
- return $fields;
- }
-
- if (isset($node_type->title_label)) {
- $fields['title'] = clone $base_field_definitions['title'];
- $fields['title']->setLabel($node_type->title_label);
- }
-
- $options = $node_type->getModuleSettings('node')['options'];
- $fields['status'] = clone $base_field_definitions['status'];
- $fields['status']->setDefaultValue(!empty($options['status']) ? NODE_PUBLISHED : NODE_NOT_PUBLISHED);
- $fields['promote'] = clone $base_field_definitions['promote'];
- $fields['promote']->setDefaultValue(!empty($options['promote']) ? NODE_PROMOTED : NODE_NOT_PROMOTED);
- $fields['sticky'] = clone $base_field_definitions['sticky'];
- $fields['sticky']->setDefaultValue(!empty($options['sticky']) ? NODE_STICKY : NODE_NOT_STICKY);
-
- return $fields;
- }
-
}
diff --git a/core/modules/node/src/Entity/NodeType.php b/core/modules/node/src/Entity/NodeType.php
index ecc21ea..740506d 100644
--- a/core/modules/node/src/Entity/NodeType.php
+++ b/core/modules/node/src/Entity/NodeType.php
@@ -76,15 +76,6 @@ class NodeType extends ConfigEntityBundleBase implements NodeTypeInterface {
public $help;
/**
- * The label to use for the title of a Node of this type in the user interface.
- *
- * @var string
- *
- * @todo Rename to $node_title_label.
- */
- public $title_label = 'Title';
-
- /**
* Indicates whether a Body field should be created for this node type.
*
* This property affects entity creation only. It allows default configuration
@@ -195,9 +186,6 @@ public static function preCreate(EntityStorageInterface $storage, array &$values
}
$values['settings']['node'] = NestedArray::mergeDeep(array(
'options' => array(
- 'status' => TRUE,
- 'promote' => TRUE,
- 'sticky' => FALSE,
'revision' => FALSE,
),
'preview' => DRUPAL_OPTIONAL,
diff --git a/core/modules/node/src/NodeTypeForm.php b/core/modules/node/src/NodeTypeForm.php
index ec00d50..14eacad 100644
--- a/core/modules/node/src/NodeTypeForm.php
+++ b/core/modules/node/src/NodeTypeForm.php
@@ -8,8 +8,10 @@
namespace Drupal\node;
use Drupal\Core\Entity\EntityForm;
+use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Component\Utility\String;
use Drupal\Core\Entity\EntityTypeInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Form controller for node type forms.
@@ -17,23 +19,53 @@
class NodeTypeForm extends EntityForm {
/**
+ * The entity manager.
+ *
+ * @var \Drupal\Core\Entity\EntityManagerInterface
+ */
+ protected $entityManager;
+
+ /**
+ * Constructs the NodeTypeForm object.
+ *
+ * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+ * The entity manager
+ */
+ public function __construct(EntityManagerInterface $entity_manager) {
+ $this->entityManager = $entity_manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('entity.manager')
+ );
+ }
+ /**
* {@inheritdoc}
*/
public function form(array $form, array &$form_state) {
$form = parent::form($form, $form_state);
$type = $this->entity;
+ $bundle_of = $type->getEntityType()->getBundleOf();
if ($this->operation == 'add') {
$form['#title'] = String::checkPlain($this->t('Add content type'));
+ $fields = $this->entityManager->getBaseFieldDefinitions($bundle_of);
+ // Create a node with a fake bundle using the type's UUID so that we can
+ // get the default values for workflow settings.
+ $node = entity_create('node', array('type' => $type->uuid()));
}
elseif ($this->operation == 'edit') {
$form['#title'] = $this->t('Edit %label content type', array('%label' => $type->label()));
+ $fields = $this->entityManager->getFieldDefinitions($bundle_of, $type->id());
+ // Create a node to get the current values for workflow settings fields.
+ $node = entity_create('node', array('type' => $type->id()));
}
$node_settings = $type->getModuleSettings('node');
- // Prepare node options to be used for 'checkboxes' form element.
- $keys = array_keys(array_filter($node_settings['options']));
- $node_settings['options'] = array_combine($keys, $keys);
$form['name'] = array(
'#title' => t('Name'),
'#type' => 'textfield',
@@ -80,7 +112,7 @@ public function form(array $form, array &$form_state) {
$form['submission']['title_label'] = array(
'#title' => t('Title field label'),
'#type' => 'textfield',
- '#default_value' => $type->title_label,
+ '#default_value' => $fields['title']->getLabel(),
'#required' => TRUE,
);
$form['submission']['preview'] = array(
@@ -105,10 +137,18 @@ public function form(array $form, array &$form_state) {
'#title' => t('Publishing options'),
'#group' => 'additional_settings',
);
+ $workflow_options = array(
+ 'status' => $node->status->value,
+ 'promote' => $node->promote->value,
+ 'sticky' => $node->sticky->value,
+ 'revision' => $type->settings['node']['options']['revision'],
+ );
+ // Prepare workflow options to be used for 'checkboxes' form element.
+ $keys = array_keys(array_filter($workflow_options));
+ $workflow_options = array_combine($keys, $keys);
$form['workflow']['options'] = array('#type' => 'checkboxes',
'#title' => t('Default options'),
- '#parents' => array('settings', 'node', 'options'),
- '#default_value' => $node_settings['options'],
+ '#default_value' => $workflow_options,
'#options' => array(
'status' => t('Published'),
'promote' => t('Promoted to front page'),
@@ -177,6 +217,7 @@ public function validate(array $form, array &$form_state) {
*/
public function save(array $form, array &$form_state) {
$type = $this->entity;
+ $type->settings['node']['options']['revision'] = $form_state['values']['options']['revision'];
$type->type = trim($type->id());
$type->name = trim($type->name);
@@ -192,6 +233,22 @@ public function save(array $form, array &$form_state) {
watchdog('node', 'Added content type %name.', $t_args, WATCHDOG_NOTICE, l(t('View'), 'admin/structure/types'));
}
+ // Update title field definition.
+ $title_field = $this->entityManager->getBundleFieldDefinition($type->getEntityType()->getBundleOf(), $type->id(), 'title');
+ if ($title_field->getLabel() != $form_state['values']['title_label']) {
+ $title_field->setLabel($form_state['values']['title_label'])->save();
+ }
+ // Update workflow options.
+ $node = entity_create('node', array('type' => $type->id()));
+ foreach (array('status', 'promote', 'sticky') as $field_name) {
+ $field = $this->entityManager->getBundleFieldDefinition($type->getEntityType()->getBundleOf(), $type->id(), $field_name);
+ $value = (bool) $form_state['values']['options'][$field_name];
+ if ($field && $node->$field_name->value != $value) {
+ $field->setDefaultValue($value)->save();
+ }
+ }
+
+ $this->entityManager->clearCachedFieldDefinitions();
$form_state['redirect_route']['route_name'] = 'node.overview_types';
}
diff --git a/core/modules/node/src/Tests/NodeCreationTest.php b/core/modules/node/src/Tests/NodeCreationTest.php
index a305ca5..1b544ba 100644
--- a/core/modules/node/src/Tests/NodeCreationTest.php
+++ b/core/modules/node/src/Tests/NodeCreationTest.php
@@ -121,7 +121,9 @@ function testUnpublishedNodeCreation() {
\Drupal::config('system.site')->set('page.front', 'test-page')->save();
// Set "Basic page" content type to be unpublished by default.
- \Drupal::config('node.type.page')->set('settings.node.options', array())->save();
+ \Drupal::entityManager()->getBundleFieldDefinition('node', 'page', 'status')
+ ->setDefaultValue(FALSE)
+ ->save();
// Create a node.
$edit = array();
diff --git a/core/modules/node/src/Tests/NodeFormButtonsTest.php b/core/modules/node/src/Tests/NodeFormButtonsTest.php
index b1f7203..1c93565 100644
--- a/core/modules/node/src/Tests/NodeFormButtonsTest.php
+++ b/core/modules/node/src/Tests/NodeFormButtonsTest.php
@@ -102,10 +102,9 @@ function testNodeFormButtons() {
// Set article content type default to unpublished. This will change the
// the initial order of buttons and/or status of the node when creating
// a node.
- /** @var \Drupal\node\NodeTypeInterface $node_type */
- $node_type = $this->container->get('entity.manager')->getStorage('node_type')->load('article');
- $node_type->settings['node']['options']['status'] = FALSE;
- $node_type->save();
+ \Drupal::entityManager()->getBundleFieldDefinition('node', 'article', 'status')
+ ->setDefaultValue(FALSE)
+ ->save();
// Verify the buttons on a node add form for an administrator.
$this->drupalLogin($this->admin_user);
diff --git a/core/modules/node/src/Tests/NodeTypeTest.php b/core/modules/node/src/Tests/NodeTypeTest.php
index 10bfbb9..8c2d128 100644
--- a/core/modules/node/src/Tests/NodeTypeTest.php
+++ b/core/modules/node/src/Tests/NodeTypeTest.php
@@ -185,6 +185,9 @@ function testNodeTypeDeletion() {
$this->assertText(t('This action cannot be undone.'), 'The node type deletion confirmation form is available.');
// Test that forum node type could not be deleted while forum active.
$this->container->get('module_handler')->install(array('forum'));
+ // Call to flush all caches after installing the forum module in the same
+ // way installing a module through the UI does.
+ $this->resetAll();
$this->drupalGet('admin/structure/types/manage/forum');
$this->assertNoLink(t('Delete'));
$this->drupalGet('admin/structure/types/manage/forum/delete');
diff --git a/core/modules/node/tests/modules/node_test_config/config/install/node.type.default.yml b/core/modules/node/tests/modules/node_test_config/config/install/node.type.default.yml
index 824ec4e..a6db87a 100644
--- a/core/modules/node/tests/modules/node_test_config/config/install/node.type.default.yml
+++ b/core/modules/node/tests/modules/node_test_config/config/install/node.type.default.yml
@@ -2,7 +2,6 @@ type: default
name: Default
description: 'Default description.'
help: ''
-title_label: Title
settings:
node:
preview: 1
diff --git a/core/modules/node/tests/modules/node_test_config/staging/node.type.import.yml b/core/modules/node/tests/modules/node_test_config/staging/node.type.import.yml
index c88d9a8..491858a 100644
--- a/core/modules/node/tests/modules/node_test_config/staging/node.type.import.yml
+++ b/core/modules/node/tests/modules/node_test_config/staging/node.type.import.yml
@@ -2,7 +2,6 @@ type: import
name: Import
description: 'Import description.'
help: ''
-title_label: Title
settings:
node:
preview: 1
diff --git a/core/profiles/standard/config/install/core.base_field_override.node.page.promote.yml b/core/profiles/standard/config/install/core.base_field_override.node.page.promote.yml
new file mode 100644
index 0000000..da7e616
--- /dev/null
+++ b/core/profiles/standard/config/install/core.base_field_override.node.page.promote.yml
@@ -0,0 +1,19 @@
+langcode: en
+status: true
+dependencies:
+ entity:
+ - node.type.page
+id: node.page.promote
+field_name: promote
+entity_type: node
+bundle: page
+label: Promote
+description: 'A boolean indicating whether the node should be displayed on the front page.'
+required: false
+translatable: true
+default_value:
+ -
+ value: 0
+default_value_function: ''
+settings: { }
+field_type: boolean
diff --git a/core/profiles/standard/config/install/node.type.article.yml b/core/profiles/standard/config/install/node.type.article.yml
index b7a7f6b..6f4f302 100644
--- a/core/profiles/standard/config/install/node.type.article.yml
+++ b/core/profiles/standard/config/install/node.type.article.yml
@@ -2,14 +2,10 @@ type: article
name: Article
description: 'Use articles for time-sensitive content like news, press releases or blog posts.'
help: ''
-title_label: Title
settings:
node:
preview: 1
options:
- status: true
- promote: true
- sticky: false
revision: false
submitted: true
status: true
diff --git a/core/profiles/standard/config/install/node.type.page.yml b/core/profiles/standard/config/install/node.type.page.yml
index 1c9cdfe..61ae323 100644
--- a/core/profiles/standard/config/install/node.type.page.yml
+++ b/core/profiles/standard/config/install/node.type.page.yml
@@ -2,14 +2,10 @@ type: page
name: 'Basic page'
description: 'Use basic pages for your static content, such as an ''About us'' page.'
help: ''
-title_label: Title
settings:
node:
preview: 1
options:
- status: true
- promote: false
- sticky: false
revision: false
submitted: false
status: true
diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php
index 0a7acf4..060126a 100644
--- a/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php
@@ -759,7 +759,18 @@ protected function setUpEntityWithFieldDefinition($custom_invoke_all = FALSE, $f
->will($this->returnValue(array()));
}
- $this->setUpEntityManager(array('test_entity_type' => $entity_type));
+ // Mock the base field definition override.
+ $override_entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
+ $override_entity_class = get_class($entity);
+ $override_entity_type->expects($this->any())
+ ->method('getClass')
+ ->will($this->returnValue($override_entity_class));
+ $storage = $this->getMock('Drupal\Core\Config\Entity\ConfigEntityStorageInterface');
+ $override_entity_type->expects($this->any())
+ ->method('getStorageClass')
+ ->will($this->returnValue(get_class($storage)));
+
+ $this->setUpEntityManager(array('test_entity_type' => $entity_type, 'bundle_field_definition' => $override_entity_type));
return $field_definition;
}
@@ -1114,9 +1125,21 @@ public function testGetFieldMap() {
->with('\Drupal\Core\Entity\ContentEntityInterface')
->will($this->returnValue(FALSE));
+ // Mock the base field definition override.
+ $override_entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
+ $override_entity_class = get_class($entity);
+ $override_entity_type->expects($this->any())
+ ->method('getClass')
+ ->will($this->returnValue($override_entity_class));
+ $storage = $this->getMock('Drupal\Core\Config\Entity\ConfigEntityStorageInterface');
+ $override_entity_type->expects($this->any())
+ ->method('getStorageClass')
+ ->will($this->returnValue(get_class($storage)));
+
$this->setUpEntityManager(array(
'test_entity_type' => $entity_type,
'non_fieldable' => $non_content_entity_type,
+ 'bundle_field_definition' => $override_entity_type,
));
$expected = array(