Commit c82feae0 authored by catch's avatar catch
Browse files

fix: #3610122 Field data for multiple cardinality fields are not populated on entity load

By: tomaston
By: catch
By: godotislate
By: bgelhard
By: oily
By: longwave
By: joe huggans
parent 2dbbdd8e
Loading
Loading
Loading
Loading
Loading
+21 −2
Changes for core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php: 21 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -1346,7 +1346,7 @@ protected function loadFromDedicatedTables(array &$values, $load_from_revision,
        $chunks = [$multiple_cardinality_fields];
      }
      foreach ($chunks as $fields) {
        $this->loadMultipleCardinalityFields($values, $id_key, $load_from_revision, $fields, $definitions, $field_columns, $field_definition_columns, $default_langcodes, $ids);
        $this->loadMultipleCardinalityFields($values, $base_table, $base_id_key, $id_key, $load_from_revision, $fields, $definitions, $field_columns, $field_definition_columns, $default_langcodes, $ids);
      }
    }
  }
@@ -1536,6 +1536,10 @@ private function loadSingleCardinalityFields(
   *
   * @param array &$values
   *   The entity values populated so far.
   * @param string $base_table
   *   The base table used to identify default translations.
   * @param string $base_id_key
   *   The ID key in the base table.
   * @param string $id_key
   *   The ID key depending on whether regular entities or revisions are being
   *   loaded.
@@ -1556,6 +1560,8 @@ private function loadSingleCardinalityFields(
   */
  private function loadMultipleCardinalityFields(
    array &$values,
    string $base_table,
    string $base_id_key,
    string $id_key,
    bool $load_from_revision,
    array $multiple_cardinality_fields,
@@ -1635,6 +1641,19 @@ private function loadMultipleCardinalityFields(
      $delta_keys[$field_name] = $query->addField($table, 'delta', $field_name . '_delta');
    }

    if ($this->langcodeKey && $this->defaultLangcodeKey) {
      if (count($multiple_cardinality_fields) > 1) {
        $join_id = '[delta_join].[id]';
        $join_langcode = '[delta_join].[langcode]';
      }
      else {
        $join_id = "[$table].[$id_key]";
        $join_langcode = "[$table].[langcode]";
      }
      $query->innerJoin($base_table, 'base', "[base].[$base_id_key] = $join_id AND [base].[$this->langcodeKey] = $join_langcode");
      $query->addField('base', $this->defaultLangcodeKey, 'base_default_langcode');
    }

    $results = $query->execute();

    foreach ($results as $row) {
@@ -1643,7 +1662,7 @@ private function loadMultipleCardinalityFields(
      // Field values in default language are stored with
      // LanguageInterface::LANGCODE_DEFAULT as key.
      $langcode = LanguageInterface::LANGCODE_DEFAULT;
      if ($this->langcodeKey && isset($default_langcodes[$value_key]) && $row['langcode'] != $default_langcodes[$value_key]) {
      if ($this->langcodeKey && empty($row['base_default_langcode']) && isset($default_langcodes[$value_key]) && $row['langcode'] != $default_langcodes[$value_key]) {
        $langcode = $row['langcode'];
      }

+1 −1
Changes for core/modules/node/tests/src/FunctionalJavascript/NodePerformanceTestAssertions/nodePromotedContentPage.sql: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@ SELECT "name", "data" FROM "config" WHERE "collection" = "" AND "name" IN ( "cor
SELECT "config"."name" AS "name" FROM "config" "config" WHERE ("collection" = "") AND ("name" LIKE "node.type.%" ESCAPE '\\') ORDER BY "collection" ASC, "name" ASC
SELECT "base"."uid" AS "uid", "base"."uuid" AS "uuid", "base"."langcode" AS "langcode" FROM "users" "base" WHERE "base"."uid" IN (0)
SELECT "users_field_data".*, "users_field_data"."langcode" AS "users_field_data__langcode" FROM "users_field_data" "users_field_data" WHERE "users_field_data"."uid" IN (0)
SELECT "user__roles"."entity_id" AS "id", "user__roles"."langcode" AS "langcode", "user__roles"."roles_target_id" AS "roles_target_id", "user__roles"."delta" AS "roles_delta" FROM "user__roles" "user__roles" WHERE ("user__roles"."entity_id" IN (0)) AND ("user__roles"."deleted" = 0) ORDER BY "user__roles"."delta" ASC
SELECT "user__roles"."entity_id" AS "id", "user__roles"."langcode" AS "langcode", "user__roles"."roles_target_id" AS "roles_target_id", "user__roles"."delta" AS "roles_delta", "base"."default_langcode" AS "base_default_langcode" FROM "user__roles" "user__roles" INNER JOIN "users_field_data" "base" ON "base"."uid" = "user__roles"."entity_id" AND "base"."langcode" = "user__roles"."langcode" WHERE ("user__roles"."entity_id" IN (0)) AND ("user__roles"."deleted" = 0) ORDER BY "user__roles"."delta" ASC
SELECT "name", "data" FROM "config" WHERE "collection" = "" AND "name" IN ( "core.date_format.medium" )
SELECT "name", "data" FROM "config" WHERE "collection" = "" AND "name" IN ( "core.date_format.long" )
SELECT "name", "route" FROM "router" WHERE "name" IN ( "entity.node.canonical" )
+118 −0
Changes for core/modules/node/tests/src/Kernel/NodeStorageLoadFieldDataTest.php: 118 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Tests\node\Kernel;

use Drupal\Core\Extension\ModuleInstallerInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\KernelTests\KernelTestBase;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
use Drupal\Tests\node\Traits\NodeCreationTrait;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;

/**
 * Tests node storage loads fields of mixed cardinality and translatability.
 */
#[Group('node')]
#[RunTestsInSeparateProcesses]
class NodeStorageLoadFieldDataTest extends KernelTestBase {

  use NodeCreationTrait;
  use ContentTypeCreationTrait;

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'field',
    'language',
    'node',
    'system',
    'user',
  ];

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $this->installEntitySchema('user');
    $this->installSchema('user', 'users_data');
    $this->installEntitySchema('node');
    $this->installSchema('node', 'node_access');
    $this->createContentType([
      'type' => 'page',
      'name' => 'Basic page',
      'display_submitted' => FALSE,
    ], FALSE);
    ConfigurableLanguage::createFromLangcode('en')
      ->save();
  }

  /**
   * Tests fields load correctly.
   */
  public function testLoadFieldData(): void {
    $fields = [
      'field_single_translatable',
      'field_single_untranslatable',
      'field_mul_translatable',
      'field_mul_untranslatable',
    ];
    foreach ($fields as $field) {
      $fieldStorage = FieldStorageConfig::create([
        'field_name' => $field,
        'entity_type' => 'node',
        'type' => 'string',
        'cardinality' => str_contains($field, 'single') ? 1 : FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
        'persist_with_no_fields' => TRUE,
      ]);
      $fieldStorage->save();

      FieldConfig::create([
        'field_storage' => $fieldStorage,
        'bundle' => 'page',
        'translatable' => !str_contains($field, 'untranslatable'),
      ])->save();
    }

    $values = [
      'field_single_translatable' => ['Single translatable'],
      'field_single_untranslatable' => ['Single untranslatable'],
      'field_mul_translatable' => [
        'Multiple translatable 1',
        'Multiple translatable 2',
      ],
      'field_mul_untranslatable' => [
        'Multiple untranslatable 1',
        'Multiple untranslatable 2',
      ],
    ];
    $id = $this->createNode($values + ['langcode' => 'en'])->id();
    $storage = \Drupal::entityTypeManager()->getStorage('node');
    $storage->resetCache();
    $node = $storage->load($id);
    $storedValues = $node->toArray();
    foreach ($values as $fieldName => $fieldValue) {
      $this->assertSame($fieldValue, array_column($storedValues[$fieldName], 'value'));
    }

    // Uninstalling the language module will delete the 'en' configurable
    // language. This will cause the langcode for the node in the node_revision
    // table to be changed to 'und'.
    // @see NodeStorage::clearRevisionsLanguage().
    \Drupal::service(ModuleInstallerInterface::class)->uninstall(['language']);
    $storage->resetCache();
    $node = $storage->load($id);
    $storedValues = $node->toArray();
    foreach ($values as $fieldName => $fieldValue) {
      $this->assertSame($fieldValue, array_column($storedValues[$fieldName], 'value'));
    }
  }

}
+1 −1
Changes for core/profiles/demo_umami/tests/src/FunctionalJavascript/MultipleRequestsPerformanceTestAssertions/umamiLogin.sql: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -11,5 +11,5 @@ SELECT * FROM "users_field_data" "u" WHERE "u"."uid" = "13" AND "u"."default_lan
SELECT "roles_target_id" FROM "user__roles" WHERE "entity_id" = "13"
SELECT "base"."uid" AS "uid", "base"."uuid" AS "uuid", "base"."langcode" AS "langcode" FROM "users" "base" WHERE "base"."uid" IN (13)
SELECT "users_field_data".*, "users_field_data"."langcode" AS "users_field_data__langcode", "user__user_picture"."user_picture_target_id" AS "user_picture_target_id", "user__user_picture"."user_picture_alt" AS "user_picture_alt", "user__user_picture"."user_picture_title" AS "user_picture_title", "user__user_picture"."user_picture_width" AS "user_picture_width", "user__user_picture"."user_picture_height" AS "user_picture_height" FROM "users_field_data" "users_field_data" LEFT OUTER JOIN "user__user_picture" "user__user_picture" ON "user__user_picture"."entity_id" = "users_field_data"."uid" AND "user__user_picture"."langcode" = "users_field_data"."langcode" AND "user__user_picture"."deleted" = 0 WHERE "users_field_data"."uid" IN (13)
SELECT "user__roles"."entity_id" AS "id", "user__roles"."langcode" AS "langcode", "user__roles"."roles_target_id" AS "roles_target_id", "user__roles"."delta" AS "roles_delta" FROM "user__roles" "user__roles" WHERE ("user__roles"."entity_id" IN (13)) AND ("user__roles"."deleted" = 0) ORDER BY "user__roles"."delta" ASC
SELECT "user__roles"."entity_id" AS "id", "user__roles"."langcode" AS "langcode", "user__roles"."roles_target_id" AS "roles_target_id", "user__roles"."delta" AS "roles_delta", "base"."default_langcode" AS "base_default_langcode" FROM "user__roles" "user__roles" INNER JOIN "users_field_data" "base" ON "base"."uid" = "user__roles"."entity_id" AND "base"."langcode" = "user__roles"."langcode" WHERE ("user__roles"."entity_id" IN (13)) AND ("user__roles"."deleted" = 0) ORDER BY "user__roles"."delta" ASC
SELECT "name", "value" FROM "key_value" WHERE "name" IN ( "theme:umami" ) AND "collection" = "config.entity.key_store.block"
+5 −5

File changed.

Preview size limit exceeded, changes collapsed.

Loading