From 62c93ff8d418876d24f5d0031327b7b93cfde23f Mon Sep 17 00:00:00 2001
From: webchick <webchick@24967.no-reply.drupal.org>
Date: Wed, 16 Jan 2013 09:37:23 -0800
Subject: [PATCH] Issue #1253820 by yched, zserno, tim.plunkett, xjm,
 effulgentsia, plach: Fixed It's impossible to submit no value for a field
 that has a default value.

---
 core/includes/entity.api.php                  | 17 +++++++
 .../Core/Entity/DatabaseStorageController.php |  4 ++
 core/lib/Drupal/Core/Entity/Entity.php        |  7 ++-
 core/lib/Drupal/Core/Entity/EntityNG.php      |  3 +-
 core/modules/comment/comment.api.php          | 15 ++++++
 .../comment/Tests/CommentApprovalTest.php     |  4 +-
 .../Drupal/comment/Tests/CommentCSSTest.php   |  1 +
 .../comment/Tests/Views/CommentTestBase.php   |  1 +
 core/modules/field/field.attach.inc           |  1 -
 core/modules/field/field.default.inc          | 31 -------------
 core/modules/field/field.module               | 37 +++++++++++++++
 .../field/Plugin/Type/Widget/WidgetBase.php   |  5 --
 .../field/Tests/FieldAttachStorageTest.php    | 13 +++++-
 .../field/lib/Drupal/field/Tests/FormTest.php | 28 ++++++++++-
 .../Drupal/field/Tests/TranslationTest.php    | 46 +++++++++++++++++++
 .../modules/field_test/field_test.entity.inc  |  3 +-
 core/modules/file/file.api.php                | 15 ++++++
 .../lib/Drupal/forum/Tests/ForumBlockTest.php |  1 +
 .../Drupal/node/Tests/NodeAccessPagerTest.php |  1 +
 core/modules/node/node.api.php                | 26 +++++++++--
 .../Tests/Entity/EntityCrudHookTest.php       |  1 +
 core/modules/taxonomy/taxonomy.api.php        | 30 ++++++++++++
 core/modules/user/user.api.php                | 15 ++++++
 .../Drupal/views/Tests/DefaultViewsTest.php   |  1 +
 24 files changed, 256 insertions(+), 50 deletions(-)

diff --git a/core/includes/entity.api.php b/core/includes/entity.api.php
index 7b42253bfee7..88e370a3cc11 100644
--- a/core/includes/entity.api.php
+++ b/core/includes/entity.api.php
@@ -55,6 +55,23 @@ function hook_entity_info_alter(&$entity_info) {
   $entity_info['node']['controller_class'] = 'Drupal\mymodule\MyCustomNodeStorageController';
 }
 
+/**
+ * Act on a newly created entity.
+ *
+ * This hook runs after a new entity object has just been instantiated. It can
+ * be used to set initial values, e.g. to provide defaults.
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ *   The entity object.
+ */
+function hook_entity_create(\Drupal\Core\Entity\EntityInterface $entity) {
+  // @todo Remove the check for EntityNG once all entity types have been
+  //   converted to it.
+  if (!isset($entity->foo) && ($entity instanceof \Drupal\Core\Entity\EntityNG)) {
+    $entity->foo->value = 'some_initial_value';
+  }
+}
+
 /**
  * Act on entities when loaded.
  *
diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
index e6d8fe6fbea6..6ccea0e171c8 100644
--- a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
+++ b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
@@ -451,6 +451,10 @@ public function create(array $values) {
       $entity->{$this->uuidKey} = $uuid->generate();
     }
 
+    // Modules might need to add or change the data initially held by the new
+    // entity object, for instance to fill-in default values.
+    $this->invokeHook('create', $entity);
+
     return $entity;
   }
 
diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php
index adbb0476b70c..5660a4da197e 100644
--- a/core/lib/Drupal/Core/Entity/Entity.php
+++ b/core/lib/Drupal/Core/Entity/Entity.php
@@ -263,7 +263,12 @@ public function access($operation = 'view', \Drupal\user\Plugin\Core\Entity\User
   public function language() {
     // @todo: Replace by EntityNG implementation once all entity types have been
     // converted to use the entity field API.
-    return !empty($this->langcode) ? language_load($this->langcode) : new Language(array('langcode' => LANGUAGE_NOT_SPECIFIED));
+    $language = language_load($this->langcode);
+    if (!$language) {
+      // Make sure we return a proper language object.
+      $language = new Language(array('langcode' => LANGUAGE_NOT_SPECIFIED));
+    }
+    return $language;
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Entity/EntityNG.php b/core/lib/Drupal/Core/Entity/EntityNG.php
index b798bcf898e3..99a29ca446e3 100644
--- a/core/lib/Drupal/Core/Entity/EntityNG.php
+++ b/core/lib/Drupal/Core/Entity/EntityNG.php
@@ -266,8 +266,7 @@ public function language() {
     $language = $this->get('langcode')->language;
     if (!$language) {
       // Make sure we return a proper language object.
-      // @todo Refactor this, see: http://drupal.org/node/1834542.
-      $language = language_default();
+      $language = new Language(array('langcode' => LANGUAGE_NOT_SPECIFIED));
     }
     return $language;
   }
diff --git a/core/modules/comment/comment.api.php b/core/modules/comment/comment.api.php
index 1762c6998335..cb4133d75129 100644
--- a/core/modules/comment/comment.api.php
+++ b/core/modules/comment/comment.api.php
@@ -48,6 +48,21 @@ function hook_comment_update(Drupal\comment\Comment $comment) {
   search_touch_node($comment->nid->value);
 }
 
+/**
+ * Act on a newly created comment.
+ *
+ * This hook runs after a new comment object has just been instantiated. It can
+ * be used to set initial values, e.g. to provide defaults.
+ *
+ * @param \Drupal\comment\Plugin\Core\Entity\Comment $comment
+ *   The comment object.
+ */
+function hook_comment_create(\Drupal\comment\Plugin\Core\Entity\Comment $comment) {
+  if (!isset($comment->foo)) {
+    $comment->foo = 'some_initial_value';
+  }
+}
+
 /**
  * Act on comments being loaded from the database.
  *
diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentApprovalTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentApprovalTest.php
index e123fc439e63..5c3f15758425 100644
--- a/core/modules/comment/lib/Drupal/comment/Tests/CommentApprovalTest.php
+++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentApprovalTest.php
@@ -47,7 +47,7 @@ function testApprovalAdminInterface() {
     // Get unapproved comment id.
     $this->drupalLogin($this->admin_user);
     $anonymous_comment4 = $this->getUnapprovedComment($subject);
-    $anonymous_comment4 = entity_create('comment', array('cid' => $anonymous_comment4, 'subject' => $subject, 'comment_body' => $body, 'nid' => $this->node->nid));
+    $anonymous_comment4 = entity_create('comment', array('cid' => $anonymous_comment4, 'node_type' => '', 'subject' => $subject, 'comment_body' => $body, 'nid' => $this->node->nid));
     $this->drupalLogout();
 
     $this->assertFalse($this->commentExists($anonymous_comment4), 'Anonymous comment was not published.');
@@ -111,7 +111,7 @@ function testApprovalNodeInterface() {
     // Get unapproved comment id.
     $this->drupalLogin($this->admin_user);
     $anonymous_comment4 = $this->getUnapprovedComment($subject);
-    $anonymous_comment4 = entity_create('comment', array('cid' => $anonymous_comment4, 'subject' => $subject, 'comment_body' => $body, 'nid' => $this->node->nid));
+    $anonymous_comment4 = entity_create('comment', array('cid' => $anonymous_comment4, 'node_type' => '', 'subject' => $subject, 'comment_body' => $body, 'nid' => $this->node->nid));
     $this->drupalLogout();
 
     $this->assertFalse($this->commentExists($anonymous_comment4), 'Anonymous comment was not published.');
diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentCSSTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentCSSTest.php
index 5cf0720a6b00..1294de4ff922 100644
--- a/core/modules/comment/lib/Drupal/comment/Tests/CommentCSSTest.php
+++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentCSSTest.php
@@ -50,6 +50,7 @@ function testCommentClasses() {
       // Add a comment.
       $comment = entity_create('comment', array(
         'nid' => $node->nid,
+        'node_type' => 'node_type_' . $node->bundle(),
         'uid' => $case['comment_uid'],
         'status' => $case['comment_status'],
         'subject' => $this->randomName(),
diff --git a/core/modules/comment/lib/Drupal/comment/Tests/Views/CommentTestBase.php b/core/modules/comment/lib/Drupal/comment/Tests/Views/CommentTestBase.php
index 476c0eff10e0..c9138839d9c1 100644
--- a/core/modules/comment/lib/Drupal/comment/Tests/Views/CommentTestBase.php
+++ b/core/modules/comment/lib/Drupal/comment/Tests/Views/CommentTestBase.php
@@ -48,6 +48,7 @@ function setUp() {
       'nid' => $this->node_user_commented->nid,
       'cid' => '',
       'pid' => '',
+      'node_type' => '',
     );
     $this->comment = entity_create('comment', $comment);
     $this->comment->save();
diff --git a/core/modules/field/field.attach.inc b/core/modules/field/field.attach.inc
index 9748b6beed4a..f9db7b5f6fde 100644
--- a/core/modules/field/field.attach.inc
+++ b/core/modules/field/field.attach.inc
@@ -1156,7 +1156,6 @@ function field_attach_presave($entity) {
  *   it leaves unspecified.
  */
 function field_attach_insert(EntityInterface $entity) {
-  _field_invoke_default('insert', $entity);
   _field_invoke('insert', $entity);
 
   // Let any module insert field data before the storage engine, accumulating
diff --git a/core/modules/field/field.default.inc b/core/modules/field/field.default.inc
index 91682650806b..27ba32c73140 100644
--- a/core/modules/field/field.default.inc
+++ b/core/modules/field/field.default.inc
@@ -53,37 +53,6 @@ function field_default_validate(EntityInterface $entity, $field, $instance, $lan
   }
 }
 
-/**
- * Inserts a default value if no $entity->$field_name entry was provided.
- *
- * This can happen with programmatic saves, or on form-based creation where
- * the current user doesn't have 'edit' permission for the field. This is the
- * default field 'insert' operation.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The entity for the operation.
- * @param $field
- *   The field structure for the operation.
- * @param $instance
- *   The instance structure for $field in $entity's bundle.
- * @param $langcode
- *   The language associated with $items.
- * @param $items
- *   An array that this function will populate with default values.
- */
-function field_default_insert(EntityInterface $entity, $field, $instance, $langcode, &$items) {
-  // _field_invoke() populates $items with an empty array if the $entity has no
-  // entry for the field, so we check on the $entity itself.
-  // We also check that the current field translation is actually defined before
-  // assigning it a default value. This way we ensure that only the intended
-  // languages get a default value. Otherwise we could have default values for
-  // not yet open languages.
-  if (empty($entity) || (!isset($entity->{$field['field_name']}[$langcode]) && !property_exists($entity, $field['field_name'])) ||
-    (isset($entity->{$field['field_name']}[$langcode]) && count($entity->{$field['field_name']}[$langcode]) == 0)) {
-    $items = field_get_default_value($entity, $field, $instance, $langcode);
-  }
-}
-
 /**
  * Copies source field values into the entity to be prepared.
  *
diff --git a/core/modules/field/field.module b/core/modules/field/field.module
index df37a5b79a68..e30f16a3fbe3 100644
--- a/core/modules/field/field.module
+++ b/core/modules/field/field.module
@@ -386,6 +386,43 @@ function field_data_type_info() {
   return $items;
 }
 
+/**
+ * Implements hook_entity_create().
+ */
+function field_entity_create(EntityInterface $entity) {
+  $info = $entity->entityInfo();
+  if (!empty($info['fieldable'])) {
+    foreach ($entity->getTranslationLanguages() as $langcode => $language) {
+      field_populate_default_values($entity, $langcode);
+    }
+  }
+}
+
+/**
+ * Inserts a default value for each entity field not having one.
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ *   The entity for the operation.
+ * @param string $langcode
+ *   (optional) The field language code to fill-in with the default value.
+ *   Defaults to the entity language.
+ */
+function field_populate_default_values(EntityInterface $entity, $langcode = NULL) {
+  $entity_type = $entity->entityType();
+  $langcode = $langcode ?: $entity->language()->langcode;
+  foreach (field_info_instances($entity_type, $entity->bundle()) as $field_name => $instance) {
+    $field = field_info_field($field_name);
+    $field_langcode = field_is_translatable($entity_type, $field) ? $langcode : LANGUAGE_NOT_SPECIFIED;
+    // We need to preserve existing values.
+    if (empty($entity->{$field_name}) || !array_key_exists($field_langcode, $entity->{$field_name})) {
+      $items = field_get_default_value($entity, $field, $instance, $field_langcode);
+      if (!empty($items)) {
+        $entity->{$field_name}[$field_langcode] = $items;
+      }
+    }
+  }
+}
+
 /**
  * Implements hook_entity_field_info() to define all configured fields.
  */
diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetBase.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetBase.php
index c4e1e26a2404..4fb8d5b544e1 100644
--- a/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetBase.php
+++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetBase.php
@@ -84,11 +84,6 @@ public function form(EntityInterface $entity, $langcode, array $items, array &$f
       $field_name => array(),
     );
 
-    // Populate widgets with default values when creating a new entity.
-    if (empty($items) && ($entity->isNew())) {
-      $items = field_get_default_value($entity, $field, $instance, $langcode);
-    }
-
     // Store field information in $form_state.
     if (!field_form_get_state($parents, $field_name, $langcode, $form_state)) {
       $field_state = array(
diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldAttachStorageTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldAttachStorageTest.php
index 1de9ffa7147d..5ae36108a801 100644
--- a/core/modules/field/lib/Drupal/field/Tests/FieldAttachStorageTest.php
+++ b/core/modules/field/lib/Drupal/field/Tests/FieldAttachStorageTest.php
@@ -340,9 +340,12 @@ function testFieldAttachSaveMissingDataDefaultValue() {
     $this->instance['default_value_function'] = 'field_test_default_value';
     field_update_instance($this->instance);
 
+    // Verify that fields are populated with default values.
     $entity_type = 'test_entity';
     $entity_init = field_test_create_entity();
     $langcode = LANGUAGE_NOT_SPECIFIED;
+    $default = field_test_default_value($entity_init, $this->field, $this->instance);
+    $this->assertEqual($entity_init->{$this->field_name}[$langcode], $default, 'Default field value correctly populated.');
 
     // Insert: Field is NULL.
     $entity = clone($entity_init);
@@ -350,6 +353,7 @@ function testFieldAttachSaveMissingDataDefaultValue() {
     field_attach_insert($entity);
 
     $entity = clone($entity_init);
+    $entity->{$this->field_name} = array();
     field_attach_load($entity_type, array($entity->ftid => $entity));
     $this->assertTrue(empty($entity->{$this->field_name}[$langcode]), 'Insert: NULL field results in no value saved');
 
@@ -359,9 +363,14 @@ function testFieldAttachSaveMissingDataDefaultValue() {
     field_attach_insert($entity);
 
     $entity = clone($entity_init);
+    $entity->{$this->field_name} = array();
     field_attach_load($entity_type, array($entity->ftid => $entity));
-    $values = field_test_default_value($entity, $this->field, $this->instance);
-    $this->assertEqual($entity->{$this->field_name}[$langcode], $values, 'Insert: missing field results in default value saved');
+    $this->assertEqual($entity->{$this->field_name}[$langcode], $default, 'Insert: missing field results in default value saved');
+
+    // Verify that prepopulated field values are not overwritten by defaults.
+    $value = array(array('value' => $default[0]['value'] - mt_rand(1, 127)));
+    $entity = entity_create('test_entity', array('fttype' => $entity_init->bundle(), $this->field_name => array($langcode => $value)));
+    $this->assertEqual($entity->{$this->field_name}[$langcode], $value, 'Prepopulated field value correctly maintained.');
   }
 
   /**
diff --git a/core/modules/field/lib/Drupal/field/Tests/FormTest.php b/core/modules/field/lib/Drupal/field/Tests/FormTest.php
index 4df7218b6165..77ac613352d6 100644
--- a/core/modules/field/lib/Drupal/field/Tests/FormTest.php
+++ b/core/modules/field/lib/Drupal/field/Tests/FormTest.php
@@ -69,7 +69,6 @@ function testFieldFormSingle() {
     $this->assertText($token_description, 'Token replacement for description is displayed');
     $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", '', 'Widget is displayed');
     $this->assertNoField("{$this->field_name}[$langcode][1][value]", 'No extraneous widget is displayed');
-    // TODO : check that the widget is populated with default value ?
 
     // Check that hook_field_widget_form_alter() does not believe this is the
     // default value form.
@@ -113,7 +112,34 @@ function testFieldFormSingle() {
     entity_get_controller('test_entity')->resetCache(array($id));
     $entity = field_test_entity_test_load($id);
     $this->assertIdentical($entity->{$this->field_name}, array(), 'Field was emptied');
+  }
 
+  /**
+   * Tests field widget default values on entity forms.
+   */
+  function testFieldFormDefaultValue() {
+    $this->field = $this->field_single;
+    $this->field_name = $this->field['field_name'];
+    $this->instance['field_name'] = $this->field_name;
+    $default = rand(1, 127);
+    $this->instance['default_value'] = array(array('value' => $default));
+    field_create_field($this->field);
+    field_create_instance($this->instance);
+    $langcode = LANGUAGE_NOT_SPECIFIED;
+
+    // Display creation form.
+    $this->drupalGet('test-entity/add/test_bundle');
+    // Test that the default value is displayed correctly.
+    $this->assertFieldByXpath("//input[@name='{$this->field_name}[$langcode][0][value]' and @value='$default']");
+
+    // Try to submit an empty value.
+    $edit = array("{$this->field_name}[$langcode][0][value]" => '');
+    $this->drupalPost(NULL, $edit, t('Save'));
+    preg_match('|test-entity/manage/(\d+)/edit|', $this->url, $match);
+    $id = $match[1];
+    $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created.');
+    $entity = field_test_entity_test_load($id);
+    $this->assertTrue(empty($entity->{$this->field_name}), 'Field is now empty.');
   }
 
   function testFieldFormSingleRequired() {
diff --git a/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php b/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php
index 8930d97d0f64..f2ada97de478 100644
--- a/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php
+++ b/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php
@@ -242,6 +242,52 @@ function testTranslatableFieldSaveLoad() {
       }
       $this->assertTrue($result, format_string('%language translation correctly handled.', array('%language' => $langcode)));
     }
+
+    // Test default values.
+    $field_name_default = drupal_strtolower($this->randomName() . '_field_name');
+    $field = $this->field;
+    $field['field_name'] = $field_name_default;
+    $instance = $this->instance;
+    $instance['field_name'] = $field_name_default;
+    $default = rand(1, 127);
+    $instance['default_value'] = array(array('value' => $default));
+    field_create_field($field);
+    field_create_instance($instance);
+    $translation_langcodes = array_slice($available_langcodes, 0, 2);
+    asort($translation_langcodes);
+    $translation_langcodes = array_values($translation_langcodes);
+
+    $eid++;
+    $evid++;
+    $values = array('eid' => $eid, 'evid' => $evid, 'fttype' => $instance['bundle'], 'langcode' => $translation_langcodes[0]);
+    foreach ($translation_langcodes as $langcode) {
+      $values[$this->field_name][$langcode] = $this->_generateTestFieldValues($this->field['cardinality']);
+    }
+    $entity = entity_create($entity_type, $values);
+
+    ksort($entity->{$field_name_default});
+    $field_langcodes = array_keys($entity->{$field_name_default});
+    $this->assertEqual($translation_langcodes, $field_langcodes, 'Missing translations did not get a default value.');
+
+    foreach ($entity->{$field_name_default} as $langcode => $items) {
+      $this->assertEqual($items, $instance['default_value'], format_string('Default value correctly populated for language %language.', array('%language' => $langcode)));
+    }
+
+    // Check that explicit empty values are not overridden with default values.
+    foreach (array(NULL, array()) as $empty_items) {
+      $eid++;
+      $evid++;
+      $values = array('eid' => $eid, 'evid' => $evid, 'fttype' => $instance['bundle'], 'langcode' => $translation_langcodes[0]);
+      foreach ($translation_langcodes as $langcode) {
+        $values[$this->field_name][$langcode] = $this->_generateTestFieldValues($this->field['cardinality']);
+        $values[$field_name_default][$langcode] = $empty_items;
+      }
+      $entity = entity_create($entity_type, $values);
+
+      foreach ($entity->{$field_name_default} as $langcode => $items) {
+        $this->assertEqual($items, $empty_items, format_string('Empty value correctly populated for language %language.', array('%language' => $langcode)));
+      }
+    }
   }
 
   /**
diff --git a/core/modules/field/tests/modules/field_test/field_test.entity.inc b/core/modules/field/tests/modules/field_test/field_test.entity.inc
index 2a7f29598f3a..aeb1634feb4f 100644
--- a/core/modules/field/tests/modules/field_test/field_test.entity.inc
+++ b/core/modules/field/tests/modules/field_test/field_test.entity.inc
@@ -121,7 +121,7 @@ function field_test_delete_bundle($bundle) {
  * Creates a basic test_entity entity.
  */
 function field_test_create_entity($id = 1, $vid = 1, $bundle = 'test_bundle', $label = '') {
-  $entity = entity_create('test_entity', array());
+  $entity = entity_create('test_entity', array('fttype' => $bundle));
   // Only set id and vid properties if they don't come as NULL (creation form).
   if (isset($id)) {
     $entity->ftid = $id;
@@ -131,7 +131,6 @@ function field_test_create_entity($id = 1, $vid = 1, $bundle = 'test_bundle', $l
     // Flag to make sure that the provided vid is used for a new revision.
     $entity->use_provided_revision_id = $vid;
   }
-  $entity->fttype = $bundle;
 
   $label = !empty($label) ? $label : $bundle . ' label';
   $entity->ftlabel = $label;
diff --git a/core/modules/file/file.api.php b/core/modules/file/file.api.php
index fb59229b977b..a6d11d5ea1cb 100644
--- a/core/modules/file/file.api.php
+++ b/core/modules/file/file.api.php
@@ -6,6 +6,21 @@
  */
 
 
+/**
+ * Act on a newly created file.
+ *
+ * This hook runs after a new file object has just been instantiated. It can be
+ * used to set initial values, e.g. to provide defaults.
+ *
+ * @param \Drupal\file\Plugin\Core\Entity\File $file
+ *   The file object.
+ */
+function hook_file_create(\Drupal\file\Plugin\Core\Entity\File $file) {
+  if (!isset($file->foo)) {
+    $file->foo = 'some_initial_value';
+  }
+}
+
 /**
  * Load additional information into file entities.
  *
diff --git a/core/modules/forum/lib/Drupal/forum/Tests/ForumBlockTest.php b/core/modules/forum/lib/Drupal/forum/Tests/ForumBlockTest.php
index 7f5405d6cde0..260b23043faf 100644
--- a/core/modules/forum/lib/Drupal/forum/Tests/ForumBlockTest.php
+++ b/core/modules/forum/lib/Drupal/forum/Tests/ForumBlockTest.php
@@ -104,6 +104,7 @@ public function testActiveForumTopicsBlock() {
       $node = $this->drupalGetNodeByTitle($topics[$index]);
       $comment = entity_create('comment', array(
         'nid' => $node->nid,
+        'node_type' => 'node_type_' . $node->bundle(),
         'subject' => $this->randomString(20),
         'comment_body' => $this->randomString(256),
         'created' => $timestamp + $index,
diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeAccessPagerTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeAccessPagerTest.php
index 4044bdcef435..de24f451c93d 100644
--- a/core/modules/node/lib/Drupal/node/Tests/NodeAccessPagerTest.php
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessPagerTest.php
@@ -47,6 +47,7 @@ public function testCommentPager() {
     for ($i = 0; $i < 60; $i++) {
       $comment = entity_create('comment', array(
         'nid' => $node->nid,
+        'node_type' => 'node_type_' . $node->bundle(),
         'subject' => $this->randomName(),
         'comment_body' => array(
           array('value' => $this->randomName()),
diff --git a/core/modules/node/node.api.php b/core/modules/node/node.api.php
index 2660e25fe0f9..10d32d0a52e6 100644
--- a/core/modules/node/node.api.php
+++ b/core/modules/node/node.api.php
@@ -16,9 +16,9 @@
  * node.module (for content types created in the user interface) or the module
  * that implements hook_node_info() to define the content type.
  *
- * During node operations (create, update, view, delete, etc.), there are
- * several sets of hooks that get invoked to allow modules to modify the base
- * node operation:
+ * During node operations (create, insert, update, view, delete, etc.), there
+ * are several sets of hooks that get invoked to allow modules to modify the
+ * base node operation:
  * - Node-type-specific hooks: These hooks are only invoked on the primary
  *   module, using the "base" return component of hook_node_info() as the
  *   function prefix.  For example, poll.module defines the base for the Poll
@@ -36,6 +36,9 @@
  *
  * Here is a list of the node and entity hooks that are invoked, field
  * operations, and other steps that take place during node operations:
+ * - Instantiating a new node:
+ *   - hook_node_create() (all)
+ *   - hook_entity_create() (all)
  * - Creating a new node (calling node_save() on a new node):
  *   - field_attach_presave()
  *   - hook_node_presave() (all)
@@ -534,6 +537,23 @@ function hook_node_insert(Drupal\node\Node $node) {
     ->execute();
 }
 
+/**
+ * Act on a newly created node.
+ *
+ * This hook runs after a new node object has just been instantiated. It can be
+ * used to set initial values, e.g. to provide defaults.
+ *
+ * @param \Drupal\node\Plugin\Core\Entity\Node $node
+ *   The node object.
+ *
+ * @ingroup node_api_hooks
+ */
+function hook_node_create(\Drupal\node\Plugin\Core\Entity\Node $node) {
+  if (!isset($node->foo)) {
+    $node->foo = 'some_initial_value';
+  }
+}
+
 /**
  * Act on arbitrary nodes being loaded from the database.
  *
diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCrudHookTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCrudHookTest.php
index 8f16e45a836a..f79512c06912 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCrudHookTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCrudHookTest.php
@@ -86,6 +86,7 @@ public function testCommentHooks() {
     $nid = $node->nid;
 
     $comment = entity_create('comment', array(
+      'node_type' => 'node_type_' . $node->bundle(),
       'cid' => NULL,
       'pid' => 0,
       'nid' => $nid,
diff --git a/core/modules/taxonomy/taxonomy.api.php b/core/modules/taxonomy/taxonomy.api.php
index 956b8b4d07bc..d2f41f661f8d 100644
--- a/core/modules/taxonomy/taxonomy.api.php
+++ b/core/modules/taxonomy/taxonomy.api.php
@@ -12,6 +12,21 @@
  * @{
  */
 
+/**
+ * Act on a newly created vocabulary.
+ *
+ * This hook runs after a new vocabulary object has just been instantiated. It
+ * can be used to set initial values, e.g. to provide defaults.
+ *
+ * @param \Drupal\taxonomy\Plugin\Core\Entity\Vocabulary $vocabulary
+ *   The vocabulary object.
+ */
+function hook_taxonomy_vocabulary_create(\Drupal\taxonomy\Plugin\Core\Entity\Vocabulary $vocabulary) {
+  if (!isset($vocabulary->synonyms)) {
+    $vocabulary->synonyms = FALSE;
+  }
+}
+
 /**
  * Act on taxonomy vocabularies when loaded.
  *
@@ -109,6 +124,21 @@ function hook_taxonomy_vocabulary_delete(Drupal\taxonomy\Plugin\Core\Entity\Voca
   }
 }
 
+/**
+ * Act on a newly created term.
+ *
+ * This hook runs after a new term object has just been instantiated. It can be
+ * used to set initial values, e.g. to provide defaults.
+ *
+ * @param \Drupal\taxonomy\Plugin\Core\Entity\Term $term
+ *   The term object.
+ */
+function hook_taxonomy_term_create(\Drupal\taxonomy\Plugin\Core\Entity\Term $term) {
+  if (!isset($term->foo)) {
+    $term->foo = 'some_initial_value';
+  }
+}
+
 /**
  * Act on taxonomy terms when loaded.
  *
diff --git a/core/modules/user/user.api.php b/core/modules/user/user.api.php
index 38e317091b7d..cae3ddec91af 100644
--- a/core/modules/user/user.api.php
+++ b/core/modules/user/user.api.php
@@ -12,6 +12,21 @@
  * @{
  */
 
+/**
+ * Act on a newly created user.
+ *
+ * This hook runs after a new user object has just been instantiated. It can be
+ * used to set initial values, e.g. to provide defaults.
+ *
+ * @param \Drupal\user\Plugin\Core\Entity\User $user
+ *   The user object.
+ */
+function hook_user_create(\Drupal\user\Plugin\Core\Entity\User $user) {
+  if (!isset($user->foo)) {
+    $user->foo = 'some_initial_value';
+  }
+}
+
 /**
  * Act on user objects when loaded from the database.
  *
diff --git a/core/modules/views/lib/Drupal/views/Tests/DefaultViewsTest.php b/core/modules/views/lib/Drupal/views/Tests/DefaultViewsTest.php
index 1e5b72ffac2c..01eaca3aa950 100644
--- a/core/modules/views/lib/Drupal/views/Tests/DefaultViewsTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/DefaultViewsTest.php
@@ -108,6 +108,7 @@ protected function setUp() {
       $comment = array(
         'uid' => $user->uid,
         'nid' => $node->nid,
+        'node_type' => 'node_type_' . $node->bundle(),
       );
       entity_create('comment', $comment)->save();
     }
-- 
GitLab