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(