Security update for Core, with self-updated composer
[yaffs-website] / web / core / lib / Drupal / Core / Entity / Sql / SqlContentEntityStorageSchema.php
index f9dd0c0850a02d6933fb24e2fac6ce31d5e65bae..9797e7bb731ffb691ad53101f53311dfef441f2d 100644 (file)
@@ -12,6 +12,7 @@ use Drupal\Core\Entity\EntityStorageException;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
 use Drupal\Core\Entity\Schema\DynamicallyFieldableEntityStorageSchemaInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
 use Drupal\Core\Field\FieldException;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
 use Drupal\field\FieldStorageConfigInterface;
@@ -201,7 +202,12 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
       return FALSE;
     }
 
-    return $this->getSchemaFromStorageDefinition($storage_definition) != $this->loadFieldSchemaData($original);
+    $current_schema = $this->getSchemaFromStorageDefinition($storage_definition);
+    $this->processFieldStorageSchema($current_schema);
+    $installed_schema = $this->loadFieldSchemaData($original);
+    $this->processFieldStorageSchema($installed_schema);
+
+    return $current_schema != $installed_schema;
   }
 
   /**
@@ -237,6 +243,12 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
    * {@inheritdoc}
    */
   public function requiresEntityDataMigration(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
+    // Check if the entity type specifies that data migration is being handled
+    // elsewhere.
+    if ($entity_type->get('requires_data_migration') === FALSE) {
+      return FALSE;
+    }
+
     // If the original storage has existing entities, or it is impossible to
     // determine if that is the case, require entity data to be migrated.
     $original_storage_class = $original->getStorageClass();
@@ -278,9 +290,8 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
     }
 
     // Create dedicated field tables.
-    $field_storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type->id());
-    $table_mapping = $this->storage->getTableMapping($field_storage_definitions);
-    foreach ($field_storage_definitions as $field_storage_definition) {
+    $table_mapping = $this->storage->getTableMapping($this->fieldStorageDefinitions);
+    foreach ($this->fieldStorageDefinitions as $field_storage_definition) {
       if ($table_mapping->requiresDedicatedTableStorage($field_storage_definition)) {
         $this->createDedicatedTableSchema($field_storage_definition);
       }
@@ -527,19 +538,18 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
       // We need to act only on shared entity schema tables.
       $table_mapping = $this->storage->getTableMapping();
       $table_names = array_diff($table_mapping->getTableNames(), $table_mapping->getDedicatedTableNames());
-      $storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
       foreach ($table_names as $table_name) {
         if (!isset($schema[$table_name])) {
           $schema[$table_name] = [];
         }
         foreach ($table_mapping->getFieldNames($table_name) as $field_name) {
-          if (!isset($storage_definitions[$field_name])) {
+          if (!isset($this->fieldStorageDefinitions[$field_name])) {
             throw new FieldException("Field storage definition for '$field_name' could not be found.");
           }
           // Add the schema for base field definitions.
-          elseif ($table_mapping->allowsSharedTableStorage($storage_definitions[$field_name])) {
+          elseif ($table_mapping->allowsSharedTableStorage($this->fieldStorageDefinitions[$field_name])) {
             $column_names = $table_mapping->getColumnNames($field_name);
-            $storage_definition = $storage_definitions[$field_name];
+            $storage_definition = $this->fieldStorageDefinitions[$field_name];
             $schema[$table_name] = array_merge_recursive($schema[$table_name], $this->getSharedTableFieldSchema($storage_definition, $table_name, $column_names));
           }
         }
@@ -560,7 +570,7 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
       // Add an index for the 'published' entity key.
       if (is_subclass_of($entity_type->getClass(), EntityPublishedInterface::class)) {
         $published_key = $entity_type->getKey('published');
-        if ($published_key && !$storage_definitions[$published_key]->hasCustomStorage()) {
+        if ($published_key && !$this->fieldStorageDefinitions[$published_key]->hasCustomStorage()) {
           $published_field_table = $table_mapping->getFieldTableName($published_key);
           $id_key = $entity_type->getKey('id');
           if ($bundle_key = $entity_type->getKey('bundle')) {
@@ -618,9 +628,8 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
     // Collect all possible field schema identifiers for shared table fields.
     // These will be used to detect entity schema data in the subsequent loop.
     $field_schema_identifiers = [];
-    $storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
-    $table_mapping = $this->storage->getTableMapping($storage_definitions);
-    foreach ($storage_definitions as $field_name => $storage_definition) {
+    $table_mapping = $this->storage->getTableMapping($this->fieldStorageDefinitions);
+    foreach ($this->fieldStorageDefinitions as $field_name => $storage_definition) {
       if ($table_mapping->allowsSharedTableStorage($storage_definition)) {
         // Make sure both base identifier names and suffixed names are listed.
         $name = $this->getFieldSchemaIdentifierName($entity_type_id, $field_name);
@@ -846,6 +855,7 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
    *   The field schema data array.
    */
   protected function saveFieldSchemaData(FieldStorageDefinitionInterface $storage_definition, $schema) {
+    $this->processFieldStorageSchema($schema);
     $this->installedStorageSchema()->set($storage_definition->getTargetEntityTypeId() . '.field_schema_data.' . $storage_definition->getName(), $schema);
   }
 
@@ -1095,6 +1105,23 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
     unset($schema['fields'][$key]['default']);
   }
 
+  /**
+   * Processes the schema for a field storage definition.
+   *
+   * @param array &$field_storage_schema
+   *   An array that contains the schema data for a field storage definition.
+   */
+  protected function processFieldStorageSchema(array &$field_storage_schema) {
+    // Clean up some schema properties that should not be taken into account
+    // after a field storage has been created.
+    foreach ($field_storage_schema as $table_name => $table_schema) {
+      foreach ($table_schema['fields'] as $key => $schema) {
+        unset($field_storage_schema[$table_name]['fields'][$key]['initial']);
+        unset($field_storage_schema[$table_name]['fields'][$key]['initial_from_field']);
+      }
+    }
+  }
+
   /**
    * Performs the specified operation on a field.
    *
@@ -1212,10 +1239,14 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
     $deleted = !$this->originalDefinitions;
     $table_mapping = $this->storage->getTableMapping();
     $table_name = $table_mapping->getDedicatedDataTableName($storage_definition, $deleted);
-    $this->database->schema()->dropTable($table_name);
+    if ($this->database->schema()->tableExists($table_name)) {
+      $this->database->schema()->dropTable($table_name);
+    }
     if ($this->entityType->isRevisionable()) {
-      $revision_name = $table_mapping->getDedicatedRevisionTableName($storage_definition, $deleted);
-      $this->database->schema()->dropTable($revision_name);
+      $revision_table_name = $table_mapping->getDedicatedRevisionTableName($storage_definition, $deleted);
+      if ($this->database->schema()->tableExists($revision_table_name)) {
+        $this->database->schema()->dropTable($revision_table_name);
+      }
     }
     $this->deleteFieldSchemaData($storage_definition);
   }
@@ -1500,7 +1531,9 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
             // involving them. Only indexes for which all columns exist are
             // actually created.
             $create = FALSE;
-            $specifier_columns = array_map(function($item) { return is_string($item) ? $item : reset($item); }, $specifier);
+            $specifier_columns = array_map(function ($item) {
+              return is_string($item) ? $item : reset($item);
+            }, $specifier);
             if (!isset($column_names) || array_intersect($specifier_columns, $column_names)) {
               $create = TRUE;
               foreach ($specifier_columns as $specifier_column_name) {
@@ -1549,7 +1582,9 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
       foreach ($index_keys as $key => $drop_method) {
         if (!empty($schema[$key])) {
           foreach ($schema[$key] as $name => $specifier) {
-            $specifier_columns = array_map(function($item) { return is_string($item) ? $item : reset($item); }, $specifier);
+            $specifier_columns = array_map(function ($item) {
+              return is_string($item) ? $item : reset($item);
+            }, $specifier);
             if (!isset($column_names) || array_intersect($specifier_columns, $column_names)) {
               $schema_handler->{$drop_method}($table_name, $name);
             }
@@ -1603,32 +1638,66 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
    *   - foreign keys: The schema definition for the foreign keys.
    *
    * @throws \Drupal\Core\Field\FieldException
-   *   Exception thrown if the schema contains reserved column names.
+   *   Exception thrown if the schema contains reserved column names or if the
+   *   initial values definition is invalid.
    */
   protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $storage_definition, $table_name, array $column_mapping) {
     $schema = [];
+    $table_mapping = $this->storage->getTableMapping();
     $field_schema = $storage_definition->getSchema();
 
     // Check that the schema does not include forbidden column names.
-    if (array_intersect(array_keys($field_schema['columns']), $this->storage->getTableMapping()->getReservedColumns())) {
+    if (array_intersect(array_keys($field_schema['columns']), $table_mapping->getReservedColumns())) {
       throw new FieldException("Illegal field column names on {$storage_definition->getName()}");
     }
 
     $field_name = $storage_definition->getName();
     $base_table = $this->storage->getBaseTable();
 
+    // Define the initial values, if any.
+    $initial_value = $initial_value_from_field = [];
+    $storage_definition_is_new = empty($this->loadFieldSchemaData($storage_definition));
+    if ($storage_definition_is_new && $storage_definition instanceof BaseFieldDefinition && $table_mapping->allowsSharedTableStorage($storage_definition)) {
+      if (($initial_storage_value = $storage_definition->getInitialValue()) && !empty($initial_storage_value)) {
+        // We only support initial values for fields that are stored in shared
+        // tables (i.e. single-value fields).
+        // @todo Implement initial value support for multi-value fields in
+        //   https://www.drupal.org/node/2883851.
+        $initial_value = reset($initial_storage_value);
+      }
+
+      if ($initial_value_field_name = $storage_definition->getInitialValueFromField()) {
+        // Check that the field used for populating initial values is valid. We
+        // must use the last installed version of that, as the new field might
+        // be created in an update function and the storage definition of the
+        // "from" field might get changed later.
+        $last_installed_storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($this->entityType->id());
+        if (!isset($last_installed_storage_definitions[$initial_value_field_name])) {
+          throw new FieldException("Illegal initial value definition on {$storage_definition->getName()}: The field $initial_value_field_name does not exist.");
+        }
+
+        if ($storage_definition->getType() !== $last_installed_storage_definitions[$initial_value_field_name]->getType()) {
+          throw new FieldException("Illegal initial value definition on {$storage_definition->getName()}: The field types do not match.");
+        }
+
+        if (!$table_mapping->allowsSharedTableStorage($last_installed_storage_definitions[$initial_value_field_name])) {
+          throw new FieldException("Illegal initial value definition on {$storage_definition->getName()}: Both fields have to be stored in the shared entity tables.");
+        }
+
+        $initial_value_from_field = $table_mapping->getColumnNames($initial_value_field_name);
+      }
+    }
+
     // A shared table contains rows for entities where the field is empty
     // (since other fields stored in the same table might not be empty), thus
     // the only columns that can be 'not null' are those for required
-    // properties of required fields. However, even those would break in the
-    // case where a new field is added to a table that contains existing rows.
-    // For now, we only hardcode 'not null' to a couple "entity keys", in order
-    // to keep their indexes optimized.
-    // @todo Revisit once we have support for 'initial' in
-    //   https://www.drupal.org/node/2346019.
+    // properties of required fields. For now, we only hardcode 'not null' to a
+    // few "entity keys", in order to keep their indexes optimized.
+    // @todo Fix this in https://www.drupal.org/node/2841291.
     $not_null_keys = $this->entityType->getKeys();
-    // Label fields are not necessarily required.
-    unset($not_null_keys['label']);
+    // Label and the 'revision_translation_affected' fields are not necessarily
+    // required.
+    unset($not_null_keys['label'], $not_null_keys['revision_translation_affected']);
     // Because entity ID and revision ID are both serial fields in the base and
     // revision table respectively, the revision ID is not known yet, when
     // inserting data into the base table. Instead the revision ID in the base
@@ -1644,6 +1713,14 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
 
       $schema['fields'][$schema_field_name] = $column_schema;
       $schema['fields'][$schema_field_name]['not null'] = in_array($field_name, $not_null_keys);
+
+      // Use the initial value of the field storage, if available.
+      if ($initial_value && isset($initial_value[$field_column_name])) {
+        $schema['fields'][$schema_field_name]['initial'] = drupal_schema_get_field_value($column_schema, $initial_value[$field_column_name]);
+      }
+      elseif (!empty($initial_value_from_field)) {
+        $schema['fields'][$schema_field_name]['initial_from_field'] = $initial_value_from_field[$field_column_name];
+      }
     }
 
     if (!empty($field_schema['indexes'])) {