From b20755f4c882dcdb2d7fb6e02cc2af38bc0aa98e Mon Sep 17 00:00:00 2001
From: Nathaniel Catchpole <catch@35733.no-reply.drupal.org>
Date: Tue, 3 Dec 2013 11:48:59 +0000
Subject: [PATCH] Issue #2142549 by amateescu, yched: Support multi-column
 field types in base tables.

---
 .../FieldableDatabaseStorageController.php    | 75 +++++++++++++++----
 .../Tests/EntityReferenceItemTest.php         |  2 +-
 .../lib/Drupal/forum/Tests/ForumTest.php      |  2 +-
 .../Field/TaxonomyTermReferenceRdfaTest.php   |  2 +-
 .../lib/Drupal/taxonomy/Entity/Term.php       | 10 +--
 .../Drupal/taxonomy/TermFormController.php    |  4 +-
 .../lib/Drupal/taxonomy/TermViewBuilder.php   |  4 +-
 .../Tests/TaxonomyTermReferenceItemTest.php   |  2 +-
 .../taxonomy/Tests/TaxonomyTestBase.php       |  8 +-
 .../lib/Drupal/taxonomy/Tests/TermTest.php    |  7 +-
 .../taxonomy/Tests/TokenReplaceTest.php       |  4 +-
 core/modules/taxonomy/taxonomy.info.yml       |  1 +
 core/modules/taxonomy/taxonomy.install        | 25 ++++++-
 core/modules/taxonomy/taxonomy.pages.inc      |  2 +-
 core/modules/taxonomy/taxonomy.tokens.inc     |  2 +-
 core/modules/taxonomy/taxonomy.views.inc      |  4 +-
 .../views/Tests/Plugin/RowEntityTest.php      |  2 +-
 17 files changed, 116 insertions(+), 40 deletions(-)

diff --git a/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php b/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php
index 712a3413ec34..8ab137712439 100644
--- a/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php
+++ b/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php
@@ -281,11 +281,21 @@ protected function mapFromStorageRecords(array $records, $load_revision = FALSE)
     $entities = array();
     foreach ($records as $id => $record) {
       $entities[$id] = array();
+      // Skip the item delta and item value levels (if possible) but let the
+      // field assign the value as suiting. This avoids unnecessary array
+      // hierarchies and saves memory here.
       foreach ($record as $name => $value) {
-        // Skip the item delta and item value levels but let the field assign
-        // the value as suiting. This avoids unnecessary array hierarchies and
-        // saves memory here.
-        $entities[$id][$name][Language::LANGCODE_DEFAULT] = $value;
+        // Handle columns named [field_name]__[column_name] (e.g for field types
+        // that store several properties).
+        if ($field_name = strstr($name, '__', TRUE)) {
+          $property_name = substr($name, strpos($name, '__') + 2);
+          $entities[$id][$field_name][Language::LANGCODE_DEFAULT][$property_name] = $value;
+        }
+        else {
+          // Handle columns named directly after the field (e.g if the field
+          // type only stores one property).
+          $entities[$id][$name][Language::LANGCODE_DEFAULT] = $value;
+        }
       }
       // If we have no multilingual values we can instantiate entity objecs
       // right now, otherwise we need to collect all the field values first.
@@ -332,13 +342,13 @@ protected function attachPropertyData(array &$entities, $revision_id = FALSE) {
       }
 
       $data = $query->execute();
-      $field_definition = \Drupal::entityManager()->getFieldDefinitions($this->entityType);
+      $field_definitions = \Drupal::entityManager()->getFieldDefinitions($this->entityType);
       $translations = array();
       if ($this->revisionDataTable) {
-        $data_fields = array_flip(array_diff(drupal_schema_fields_sql($this->entityInfo['revision_data_table']), drupal_schema_fields_sql($this->entityInfo['base_table'])));
+        $data_column_names = array_flip(array_diff(drupal_schema_fields_sql($this->entityInfo['revision_data_table']), drupal_schema_fields_sql($this->entityInfo['base_table'])));
       }
       else {
-        $data_fields = array_flip(drupal_schema_fields_sql($this->entityInfo['data_table']));
+        $data_column_names = array_flip(drupal_schema_fields_sql($this->entityInfo['data_table']));
       }
 
       foreach ($data as $values) {
@@ -349,9 +359,24 @@ protected function attachPropertyData(array &$entities, $revision_id = FALSE) {
         $langcode = empty($values['default_langcode']) ? $values['langcode'] : Language::LANGCODE_DEFAULT;
         $translations[$id][$langcode] = TRUE;
 
-        foreach ($field_definition as $name => $definition) {
-          if (isset($data_fields[$name])) {
-            $entities[$id][$name][$langcode] = $values[$name];
+        foreach (array_keys($field_definitions) as $field_name) {
+          // Handle columns named directly after the field.
+          if (isset($data_column_names[$field_name])) {
+            $entities[$id][$field_name][$langcode] = $values[$field_name];
+          }
+          else {
+            // @todo Change this logic to be based on a mapping of field
+            // definition properties (translatability, revisionability) in
+            // https://drupal.org/node/2144631.
+            foreach ($data_column_names as $data_column_name) {
+              // Handle columns named [field_name]__[column_name], for which we
+              // need to look through all column names from the table that start
+              // with the name of the field.
+              if (($data_field_name = strstr($data_column_name, '__', TRUE)) && $data_field_name === $field_name) {
+                $property_name = substr($data_column_name, strpos($data_column_name, '__') + 2);
+                $entities[$id][$field_name][$langcode][$property_name] = $values[$data_column_name];
+              }
+            }
           }
         }
       }
@@ -744,17 +769,41 @@ protected function savePropertyData(EntityInterface $entity, $table_key = 'data_
    */
   protected function mapToStorageRecord(EntityInterface $entity, $table_key = 'base_table') {
     $record = new \stdClass();
+    $values = array();
     $definitions = $entity->getPropertyDefinitions();
     $schema = drupal_get_schema($this->entityInfo[$table_key]);
     $is_new = $entity->isNew();
 
+    $multi_column_fields = array();
     foreach (drupal_schema_fields_sql($this->entityInfo[$table_key]) as $name) {
-      $info = $schema['fields'][$name];
-      $value = isset($definitions[$name]) && isset($entity->$name->value) ? $entity->$name->value : NULL;
+      // Check for fields which store data in multiple columns and process them
+      // separately.
+      if ($field = strstr($name, '__', TRUE)) {
+        $multi_column_fields[$field] = TRUE;
+        continue;
+      }
+      $values[$name] = isset($definitions[$name]) && isset($entity->$name->value) ? $entity->$name->value : NULL;
+    }
+
+    // Handle fields that store multiple properties and match each property name
+    // to its schema column name.
+    foreach (array_keys($multi_column_fields) as $field_name) {
+      $field_items = $entity->get($field_name);
+      $field_value = $field_items->getValue();
+      // @todo Reconsider the usage of getPropertyDefinitions() after
+      // https://drupal.org/node/2144327.
+      foreach (array_keys($field_items[0]->getPropertyDefinitions()) as $property_name) {
+        if (isset($schema['fields'][$field_name . '__' . $property_name])) {
+          $values[$field_name . '__' . $property_name] = isset($field_value[0][$property_name]) ? $field_value[0][$property_name] : NULL;
+        }
+      }
+    }
+
+    foreach ($values as $field_name => $value) {
       // If we are creating a new entity, we must not populate the record with
       // NULL values otherwise defaults would not be applied.
       if (isset($value) || !$is_new) {
-        $record->$name = drupal_schema_get_field_value($info, $value);
+        $record->$field_name = drupal_schema_get_field_value($schema['fields'][$field_name], $value);
       }
     }
 
diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/Tests/EntityReferenceItemTest.php b/core/modules/entity_reference/lib/Drupal/entity_reference/Tests/EntityReferenceItemTest.php
index a638245977ba..879c02b3b0f9 100644
--- a/core/modules/entity_reference/lib/Drupal/entity_reference/Tests/EntityReferenceItemTest.php
+++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Tests/EntityReferenceItemTest.php
@@ -22,7 +22,7 @@ class EntityReferenceItemTest extends FieldUnitTestBase {
    *
    * @var array
    */
-  public static $modules = array('entity_reference', 'taxonomy', 'options');
+  public static $modules = array('entity_reference', 'taxonomy', 'options', 'text', 'filter');
 
   /**
    * The taxonomy vocabulary to test with.
diff --git a/core/modules/forum/lib/Drupal/forum/Tests/ForumTest.php b/core/modules/forum/lib/Drupal/forum/Tests/ForumTest.php
index 8e7bbb46461b..caa03e112493 100644
--- a/core/modules/forum/lib/Drupal/forum/Tests/ForumTest.php
+++ b/core/modules/forum/lib/Drupal/forum/Tests/ForumTest.php
@@ -387,7 +387,7 @@ function createForum($type, $parent = 0) {
     );
 
     // Verify forum.
-    $term = db_query("SELECT * FROM {taxonomy_term_data} t WHERE t.vid = :vid AND t.name = :name AND t.description = :desc", array(':vid' => \Drupal::config('forum.settings')->get('vocabulary'), ':name' => $name, ':desc' => $description))->fetchAssoc();
+    $term = db_query("SELECT * FROM {taxonomy_term_data} t WHERE t.vid = :vid AND t.name = :name AND t.description__value = :desc", array(':vid' => \Drupal::config('forum.settings')->get('vocabulary'), ':name' => $name, ':desc' => $description))->fetchAssoc();
     $this->assertTrue(!empty($term), 'The ' . $type . ' exists in the database');
 
     // Verify forum hierarchy.
diff --git a/core/modules/rdf/lib/Drupal/rdf/Tests/Field/TaxonomyTermReferenceRdfaTest.php b/core/modules/rdf/lib/Drupal/rdf/Tests/Field/TaxonomyTermReferenceRdfaTest.php
index 274b5aacafc4..2a3eb4a42624 100644
--- a/core/modules/rdf/lib/Drupal/rdf/Tests/Field/TaxonomyTermReferenceRdfaTest.php
+++ b/core/modules/rdf/lib/Drupal/rdf/Tests/Field/TaxonomyTermReferenceRdfaTest.php
@@ -37,7 +37,7 @@ class TaxonomyTermReferenceRdfaTest extends FieldRdfaTestBase {
   /**
    * {@inheritdoc}
    */
-  public static $modules = array('taxonomy');
+  public static $modules = array('taxonomy', 'options', 'text', 'filter');
 
   public static function getInfo() {
     return array(
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Term.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Term.php
index 798763590b78..7e6c9e9c04e8 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Term.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Term.php
@@ -224,14 +224,10 @@ public static function baseFieldDefinitions($entity_type) {
       ->setLabel(t('Name'))
       ->setDescription(t('The term name.'));
 
-    $fields['description'] = FieldDefinition::create('string')
+    $fields['description'] = FieldDefinition::create('text_long')
       ->setLabel(t('Description'))
-      ->setDescription(t('A description of the term.'));
-
-    // @todo Combine with description.
-    $fields['format'] = FieldDefinition::create('string')
-      ->setLabel(t('Description format'))
-      ->setDescription(t('The filter format ID of the description.'));
+      ->setDescription(t('A description of the term.'))
+      ->setFieldSetting('text_processing', 1);
 
     $fields['weight'] = FieldDefinition::create('integer')
       ->setLabel(t('Weight'))
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/TermFormController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/TermFormController.php
index e7fb41edebed..bbed7fdc9310 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/TermFormController.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/TermFormController.php
@@ -74,7 +74,7 @@ public function form(array $form, array &$form_state) {
       '#type' => 'text_format',
       '#title' => $this->t('Description'),
       '#default_value' => $term->description->value,
-      '#format' => $term->format->value,
+      '#format' => $term->description->format,
       '#weight' => 0,
     );
     $language_configuration = $this->moduleHandler->moduleExists('language') ? language_get_default_configuration('taxonomy_term', $vocabulary->id()) : FALSE;
@@ -178,7 +178,7 @@ public function buildEntity(array $form, array &$form_state) {
     // \Drupal\Core\Entity\Entity::save() method.
     $description = $form_state['values']['description'];
     $term->description->value = $description['value'];
-    $term->format->value = $description['format'];
+    $term->description->format = $description['format'];
 
     // Assign parents with proper delta values starting from 0.
     $term->parent = array_keys($form_state['values']['parent']);
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/TermViewBuilder.php b/core/modules/taxonomy/lib/Drupal/taxonomy/TermViewBuilder.php
index b33dbfd3dc1d..af56597500f0 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/TermViewBuilder.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/TermViewBuilder.php
@@ -24,10 +24,12 @@ public function buildContent(array $entities, array $displays, $view_mode, $lang
 
     foreach ($entities as $entity) {
       // Add the description if enabled.
+      // @todo Remove this when base fields are able to use formatters.
+      // https://drupal.org/node/2144919
       $display = $displays[$entity->bundle()];
       if (!empty($entity->description->value) && $display->getComponent('description')) {
         $entity->content['description'] = array(
-          '#markup' => check_markup($entity->description->value, $entity->format->value, '', TRUE),
+          '#markup' => $entity->description->processed,
           '#prefix' => '<div class="taxonomy-term-description">',
           '#suffix' => '</div>',
         );
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TaxonomyTermReferenceItemTest.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TaxonomyTermReferenceItemTest.php
index ee6bf42e1dad..a9a5e12a67f4 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TaxonomyTermReferenceItemTest.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TaxonomyTermReferenceItemTest.php
@@ -23,7 +23,7 @@ class TaxonomyTermReferenceItemTest extends FieldUnitTestBase {
    *
    * @var array
    */
-  public static $modules = array('taxonomy', 'options');
+  public static $modules = array('taxonomy', 'options', 'text', 'filter');
 
   public static function getInfo() {
     return array(
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TaxonomyTestBase.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TaxonomyTestBase.php
index 4ae48235d5a1..9fd5a3415cca 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TaxonomyTestBase.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TaxonomyTestBase.php
@@ -55,9 +55,11 @@ function createTerm($vocabulary) {
     $format = array_pop($filter_formats);
     $term = entity_create('taxonomy_term', array(
       'name' => $this->randomName(),
-      'description' => $this->randomName(),
-      // Use the first available text format.
-      'format' => $format->format,
+      'description' => array(
+        'value' => $this->randomName(),
+        // Use the first available text format.
+        'format' => $format->format,
+      ),
       'vid' => $vocabulary->id(),
       'langcode' => Language::LANGCODE_NOT_SPECIFIED,
     ));
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php
index dcae350b53f3..c6b21e4d11d7 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php
@@ -346,11 +346,16 @@ function testTermInterface() {
     // Did this page request display a 'term-listing-heading'?
     $this->assertPattern('|class="taxonomy-term-description"|', 'Term page displayed the term description element.');
     // Check that it does NOT show a description when description is blank.
-    $term->description = '';
+    $term->description->value = NULL;
     $term->save();
     $this->drupalGet('taxonomy/term/' . $term->id());
     $this->assertNoPattern('|class="taxonomy-term-description"|', 'Term page did not display the term description when description was blank.');
 
+    // Check that the description value is processed.
+    $term->description->value = $value = $this->randomName();
+    $term->save();
+    $this->assertEqual($term->description->processed, "<p>$value</p>\n");
+
     // Check that the term feed page is working.
     $this->drupalGet('taxonomy/term/' . $term->id() . '/feed');
 
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TokenReplaceTest.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TokenReplaceTest.php
index be4ed5371190..a91675c0249a 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TokenReplaceTest.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TokenReplaceTest.php
@@ -88,7 +88,7 @@ function testTaxonomyTokenReplacement() {
     $tests = array();
     $tests['[term:tid]'] = $term1->id();
     $tests['[term:name]'] = check_plain($term1->name->value);
-    $tests['[term:description]'] = check_markup($term1->description->value, $term1->format->value);
+    $tests['[term:description]'] = $term1->description->processed;
     $tests['[term:url]'] = url('taxonomy/term/' . $term1->id(), array('absolute' => TRUE));
     $tests['[term:node-count]'] = 0;
     $tests['[term:parent:name]'] = '[term:parent:name]';
@@ -103,7 +103,7 @@ function testTaxonomyTokenReplacement() {
     $tests = array();
     $tests['[term:tid]'] = $term2->id();
     $tests['[term:name]'] = check_plain($term2->name->value);
-    $tests['[term:description]'] = check_markup($term2->description->value, $term2->format->value);
+    $tests['[term:description]'] = $term2->description->processed;
     $tests['[term:url]'] = url('taxonomy/term/' . $term2->id(), array('absolute' => TRUE));
     $tests['[term:node-count]'] = 1;
     $tests['[term:parent:name]'] = check_plain($term1->name->value);
diff --git a/core/modules/taxonomy/taxonomy.info.yml b/core/modules/taxonomy/taxonomy.info.yml
index 88de323ffb15..2fe12f0b623c 100644
--- a/core/modules/taxonomy/taxonomy.info.yml
+++ b/core/modules/taxonomy/taxonomy.info.yml
@@ -6,4 +6,5 @@ version: VERSION
 core: 8.x
 dependencies:
   - options
+  - text
 configure: taxonomy.vocabulary_list
diff --git a/core/modules/taxonomy/taxonomy.install b/core/modules/taxonomy/taxonomy.install
index 086c030e40a8..f7abc206cd8e 100644
--- a/core/modules/taxonomy/taxonomy.install
+++ b/core/modules/taxonomy/taxonomy.install
@@ -48,13 +48,13 @@ function taxonomy_schema() {
         'default' => '',
         'description' => 'The term name.',
       ),
-      'description' => array(
+      'description__value' => array(
         'type' => 'text',
         'not null' => FALSE,
         'size' => 'big',
         'description' => 'A description of the term.',
       ),
-      'format' => array(
+      'description__format' => array(
         'type' => 'varchar',
         'length' => 255,
         'not null' => FALSE,
@@ -428,3 +428,24 @@ function taxonomy_update_8009() {
     }
   }
 }
+
+/**
+ * Rename the 'description' and 'format' columns for taxonomy terms.
+ */
+function taxonomy_update_8010() {
+  $description = array(
+    'type' => 'text',
+    'not null' => FALSE,
+    'size' => 'big',
+    'description' => 'A description of the term.',
+  );
+  db_change_field('taxonomy_term_data', 'description', 'description__value', $description);
+
+  $format = array(
+    'type' => 'varchar',
+    'length' => 255,
+    'not null' => FALSE,
+    'description' => 'The filter format ID of the description.',
+  );
+  db_change_field('taxonomy_term_data', 'format', 'description__format', $format);
+}
diff --git a/core/modules/taxonomy/taxonomy.pages.inc b/core/modules/taxonomy/taxonomy.pages.inc
index c327191cbecc..79d44722157d 100644
--- a/core/modules/taxonomy/taxonomy.pages.inc
+++ b/core/modules/taxonomy/taxonomy.pages.inc
@@ -73,7 +73,7 @@ function taxonomy_term_feed(Term $term) {
   $channel['title'] = \Drupal::config('system.site')->get('name') . ' - ' . $term->label();
   // Only display the description if we have a single term, to avoid clutter and confusion.
   // HTML will be removed from feed description.
-  $channel['description'] = check_markup($term->description->value, $term->format->value, '', TRUE);
+  $channel['description'] = $term->description->processed;
   $nids = taxonomy_select_nodes($term->id(), FALSE, \Drupal::config('system.rss')->get('items.limit'));
 
   return node_feed($nids, $channel);
diff --git a/core/modules/taxonomy/taxonomy.tokens.inc b/core/modules/taxonomy/taxonomy.tokens.inc
index ad0a1d4fa6da..ce77a7cc1eed 100644
--- a/core/modules/taxonomy/taxonomy.tokens.inc
+++ b/core/modules/taxonomy/taxonomy.tokens.inc
@@ -108,7 +108,7 @@ function taxonomy_tokens($type, $tokens, array $data = array(), array $options =
           break;
 
         case 'description':
-          $replacements[$original] = $sanitize ? check_markup($term->description->value, $term->format->value, '', TRUE) : $term->description->value;
+          $replacements[$original] = $sanitize ? $term->description->processed : $term->description->value;
           break;
 
         case 'url':
diff --git a/core/modules/taxonomy/taxonomy.views.inc b/core/modules/taxonomy/taxonomy.views.inc
index 3404b3ab25a9..6e064903ac6b 100644
--- a/core/modules/taxonomy/taxonomy.views.inc
+++ b/core/modules/taxonomy/taxonomy.views.inc
@@ -128,12 +128,12 @@ function taxonomy_views_data() {
   );
 
   // Term description
-  $data['taxonomy_term_data']['description'] = array(
+  $data['taxonomy_term_data']['description__value'] = array(
     'title' => t('Term description'),
     'help' => t('The description associated with a taxonomy term.'),
     'field' => array(
       'id' => 'markup',
-      'format' => array('field' => 'format'),
+      'format' => array('field' => 'description__format'),
       'click sortable' => FALSE,
     ),
     'filter' => array(
diff --git a/core/modules/views/lib/Drupal/views/Tests/Plugin/RowEntityTest.php b/core/modules/views/lib/Drupal/views/Tests/Plugin/RowEntityTest.php
index 44861576c3c4..d71c28315593 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Plugin/RowEntityTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Plugin/RowEntityTest.php
@@ -21,7 +21,7 @@ class RowEntityTest extends ViewUnitTestBase {
    *
    * @var array
    */
-  public static $modules = array('taxonomy', 'field', 'entity', 'system');
+  public static $modules = array('taxonomy', 'text', 'filter', 'field', 'entity', 'system');
 
   /**
    * Views used by this test.
-- 
GitLab