diff --git a/src/Element/Tablefield.php b/src/Element/Tablefield.php
index 89ea705..1002bd1 100644
--- a/src/Element/Tablefield.php
+++ b/src/Element/Tablefield.php
@@ -28,6 +28,7 @@ class Tablefield extends FormElement {
'#input_type' => 'textfield',
'#rebuild' => FALSE,
'#import' => FALSE,
+ '#editor' => FALSE,
'#paste' => FALSE,
'#process' => [
[$class, 'processTablefield'],
@@ -108,24 +109,35 @@ class Tablefield extends FormElement {
$draggable = TRUE;
for ($ii = 0; $ii < $cols; $ii++) {
if (!empty($element['#locked_cells'][$i][$ii]) && !empty($element['#lock'])) {
+ if (is_array($element['#locked_cells'][$i][$ii]) && isset($element['#locked_cells'][$i][$ii]['value']) && isset($element['#locked_cells'][$i][$ii]['format'])) {
+ $cell_value = $element['#locked_cells'][$i][$ii]['value'];
+ $cell_format = $element['#locked_cells'][$i][$ii]['format'];
+ }
+ else {
+ $cell_value = $element['#locked_cells'][$i][$ii];
+ $cell_format = NULL;
+ }
+ $cell_rendered = !empty($cell_format) ? check_markup($cell_value, $cell_format) : $cell_value;
$draggable = FALSE;
$weightedRows[$i][$ii] = [
'#type' => 'item',
- '#value' => $element['#locked_cells'][$i][$ii],
- '#title' => $element['#locked_cells'][$i][$ii],
+ '#value' => $cell_rendered,
+ '#title' => $cell_rendered,
];
}
else {
$cell_value = $value[$i][$ii] ?? '';
+ $cell_rendered = is_array($cell_value) && isset($cell_value['value']) ? $cell_value['value'] : $cell_value;
$weightedRows[$i][$ii] = [
- '#type' => $input_type,
+ '#type' => $element['#editor'] ? 'text_format' : $input_type,
'#maxlength' => 2048,
'#size' => 0,
'#attributes' => [
'class' => ['tablefield-row-' . $i, 'tablefield-col-' . $ii],
'style' => 'width:100%',
],
- '#default_value' => $cell_value,
+ '#default_value' => $cell_rendered,
+ '#format' => is_array($cell_value) && isset($cell_value['format']) ? $cell_value['format'] : NULL,
];
}
}
@@ -345,8 +357,7 @@ class Tablefield extends FormElement {
$parents = array_slice($triggering_element['#array_parents'], 0, -2, TRUE);
$rebuild = NestedArray::getValue($form, $parents);
- // We don't want to re-send the format/_weight options.
- unset($rebuild['format']);
+ // We don't want to re-send the _weight option.
unset($rebuild['_weight']);
// Set row value to default only if there is Add Row button clicked.
@@ -388,6 +399,25 @@ class Tablefield extends FormElement {
$imported_tablefield = static::importCsv($id);
if ($imported_tablefield) {
+ $array_parents = array_slice($triggering_element['#array_parents'], 0, -2, TRUE);
+ $element = NestedArray::getValue($form, $array_parents);
+
+ $array_element_parents = array_slice($triggering_element['#array_parents'], 0, -3, TRUE);
+ $element_parents = NestedArray::getValue($form, $array_element_parents);
+
+ $value = $imported_tablefield['table'];
+ foreach ($value as $row => $row_value) {
+ foreach ($row_value as $col => $col_value) {
+ $col_el = $element['table'][$row][$col];
+ if ((empty($col_el) && $element_parents['#editor']) || (!empty($col_el) && $col_el['#type'] == 'text_format')) {
+ $imported_tablefield['table'][$row][$col] = [
+ 'format' => $col_el['#format'],
+ 'value' => $col_value
+ ];
+ }
+ }
+ }
+
$form_state->setValue($parents, $imported_tablefield);
$input = $form_state->getUserInput();
diff --git a/src/Plugin/Field/FieldFormatter/TablefieldFormatter.php b/src/Plugin/Field/FieldFormatter/TablefieldFormatter.php
index d2555a4..0947d6f 100644
--- a/src/Plugin/Field/FieldFormatter/TablefieldFormatter.php
+++ b/src/Plugin/Field/FieldFormatter/TablefieldFormatter.php
@@ -155,8 +155,10 @@ class TablefieldFormatter extends FormatterBase implements ContainerFactoryPlugi
foreach ($tabledata as $row_key => $row) {
foreach ($row as $col_key => $cell) {
if (is_numeric($col_key)) {
+ $value = is_array($cell) && isset($cell['value']) ? $cell['value'] : $cell;
+ $format = is_array($cell) && isset($cell['format']) ? $cell['format'] : NULL;
$tabledata[$row_key][$col_key] = [
- 'data' => empty($table->format) ? $cell : check_markup($cell, $table->format),
+ 'data' => empty($format) ? $value : check_markup($value, $format),
'class' => ['row_' . $row_key, 'col_' . $col_key],
];
}
diff --git a/src/Plugin/Field/FieldType/TablefieldItem.php b/src/Plugin/Field/FieldType/TablefieldItem.php
index 38fc2c9..c6ba579 100644
--- a/src/Plugin/Field/FieldType/TablefieldItem.php
+++ b/src/Plugin/Field/FieldType/TablefieldItem.php
@@ -35,15 +35,10 @@ class TablefieldItem extends FieldItemBase {
'size' => 'big',
'serialize' => TRUE,
],
- 'format' => [
- 'type' => 'varchar',
- 'length' => 255,
- 'default value' => '',
- ],
'caption' => [
'type' => 'varchar',
'length' => 255,
- 'default value' => '',
+ 'default' => '',
],
],
];
@@ -141,9 +136,6 @@ class TablefieldItem extends FieldItemBase {
->setLabel(t('Table data'))
->setDescription(t('Stores tabular data.'));
- $properties['format'] = DataDefinition::create('filter_format')
- ->setLabel(t('Text format'));
-
$properties['caption'] = DataDefinition::create('string')
->setLabel(t('Table Caption'));
diff --git a/src/Plugin/Field/FieldWidget/TablefieldWidget.php b/src/Plugin/Field/FieldWidget/TablefieldWidget.php
index 8eb5ba0..7541a34 100644
--- a/src/Plugin/Field/FieldWidget/TablefieldWidget.php
+++ b/src/Plugin/Field/FieldWidget/TablefieldWidget.php
@@ -176,10 +176,7 @@ class TablefieldWidget extends WidgetBase implements ContainerFactoryPluginInter
// Allow the user to select input filters.
if (!empty($field_settings['cell_processing'])) {
- $element['#base_type'] = $element['#type'];
- $element['#type'] = 'text_format';
- $element['#format'] = $default_value->format ?? NULL;
- $element['#editor'] = FALSE;
+ $element['#editor'] = TRUE;
}
return $element;
diff --git a/tablefield.install b/tablefield.install
index d5ea4fb..1e2e7de 100644
--- a/tablefield.install
+++ b/tablefield.install
@@ -5,6 +5,8 @@
* Installation options for TableField.
*/
+use Drupal\Core\Entity\Sql\SqlEntityStorageInterface;
+
/**
* Add columns for caption field to the database.
*/
@@ -13,39 +15,149 @@ function tablefield_update_8001() {
'type' => 'varchar',
'length' => 255,
'default' => '',
- 'not null' => TRUE,
+ 'not null' => FALSE,
]);
}
+/**
+ * Remove "format" column because each cell will use its own format.
+ */
+function tablefield_update_8002() {
+ tablefield_remove_existing_column('format');
+}
+
/**
* Helper function to add new columns to the field schema.
*
- * @param string $column_name
+ * @param string $new_property_name
* The name of the column that will be added.
* @param array $spec
* The options of the new column.
*/
-function tablefield_add_new_column($column_name, array $spec) {
- $field_map = \Drupal::service('entity_field.manager')->getFieldMapByFieldType('tablefield');
+function tablefield_add_new_column(string $new_property_name, array $spec) {
$schema = \Drupal::database()->schema();
+ $entity_type_manager = \Drupal::entityTypeManager();
+ $entity_field_manager = \Drupal::service('entity_field.manager');
+ $entity_field_map = $entity_field_manager->getFieldMapByFieldType('tablefield');
+ $entity_storage_schema_sql = \Drupal::keyValue('entity.storage_schema.sql');
+ /** @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $last_installed_schema_repository */
+ $last_installed_schema_repository = \Drupal::service('entity.last_installed_schema.repository');
- foreach ($field_map as $entity_type_id => $fields) {
- foreach (array_keys($fields) as $field_name) {
- $tables = [
- "{$entity_type_id}__$field_name",
- "{$entity_type_id}_revision__$field_name",
- ];
+ foreach ($entity_field_map as $entity_type_id => $fields) {
+ $entity_storage = $entity_type_manager->getStorage($entity_type_id);
+ if (!$entity_storage instanceof SqlEntityStorageInterface) {
+ continue;
+ }
+
+ /** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */
+ $entity_type = $entity_type_manager->getDefinition($entity_type_id);
+ // Loads definitions for all fields.
+ $entity_field_storage_defintitions = $entity_field_manager->getFieldStorageDefinitions($entity_type_id);
+ /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
+ $table_mapping = $entity_storage->getTableMapping($entity_field_storage_defintitions);
- $new_column_name = $field_name . '_' . $column_name;
+ // Intersect tablefield fields with storage definitions for all
+ // fields.
+ /** @var \Drupal\Core\Field\FieldStorageDefinitionInterface[] $field_definitions */
+ $field_definitions = array_intersect_key($entity_field_storage_defintitions, $fields);
+
+ // Iterate over all tablefield field definitions for this entity type.
+ foreach ($field_definitions as $field_definition) {
+ $field_name = $field_definition->getName();
+ $tables = [];
+ $tables[] = $table_mapping->getFieldTableName($field_name);
+ if ($entity_type->isRevisionable() && $field_definition->isRevisionable()) {
+ $tables[] = $table_mapping->getDedicatedRevisionTableName($field_definition);
+ }
+
+ // Field type column names map to real table column names.
+ $columns = $table_mapping->getColumnNames($field_name);
+ $column_name = $columns[$new_property_name];
foreach ($tables as $table) {
- $field_exists = $schema->fieldExists($table, $new_column_name);
- $table_exists = $schema->tableExists($table);
+ if (!$schema->fieldExists($table, $column_name)) {
+ $schema->addField($table, $column_name, $spec);
+ }
+ }
+
+ // Update the tracked entity table schema.
+ $schema_key = "$entity_type_id.field_schema_data.$field_name";
+ $field_schema_data = $entity_storage_schema_sql->get($schema_key);
+ foreach ($field_schema_data as $table_name => $field_schema) {
+ // Remove the column from the field schema data.
+ $field_schema_data[$table_name]['fields'][$column_name] = $spec;
+ }
+ $entity_storage_schema_sql->set($schema_key, $field_schema_data);
+
+ $definitions = $last_installed_schema_repository->getLastInstalledFieldStorageDefinitions($entity_type_id);
+ $definitions[$field_name] = $field_definition;
+ $last_installed_schema_repository->setLastInstalledFieldStorageDefinitions($entity_type_id, $definitions);
+ }
+ }
+}
+
+/**
+ * Helper function to remove columns from the field schema.
+ *
+ * @param string $property_to_remove
+ * The name of the field that will be added.
+ */
+function tablefield_remove_existing_column(string $property_to_remove) {
+ $schema = \Drupal::database()->schema();
+ $entity_type_manager = \Drupal::entityTypeManager();
+ $entity_field_manager = \Drupal::service('entity_field.manager');
+ $entity_field_map = $entity_field_manager->getFieldMapByFieldType('tablefield');
+ $entity_storage_schema_sql = \Drupal::keyValue('entity.storage_schema.sql');
+ /** @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $last_installed_schema_repository */
+ $last_installed_schema_repository = \Drupal::service('entity.last_installed_schema.repository');
+
+ foreach ($entity_field_map as $entity_type_id => $fields) {
+ $entity_storage = $entity_type_manager->getStorage($entity_type_id);
+ if (!$entity_storage instanceof SqlEntityStorageInterface) {
+ continue;
+ }
+
+ /** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */
+ $entity_type = $entity_type_manager->getDefinition($entity_type_id);
+ // Loads definitions for all fields.
+ $entity_field_storage_defintitions = $entity_field_manager->getFieldStorageDefinitions($entity_type_id);
+ /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
+ $table_mapping = $entity_storage->getTableMapping($entity_field_storage_defintitions);
- if (!$field_exists && $table_exists) {
- $schema->addField($table, $new_column_name, $spec);
+ // Intersect tablefield fields with storage definitions for all
+ // fields.
+ /** @var \Drupal\Core\Field\FieldStorageDefinitionInterface[] $field_definitions */
+ $field_definitions = array_intersect_key($entity_field_storage_defintitions, $fields);
+
+ // Iterate over all tablefield field definitions for this entity type.
+ foreach ($field_definitions as $field_definition) {
+ $field_name = $field_definition->getName();
+ $tables = [];
+ $tables[] = $table_mapping->getFieldTableName($field_name);
+ if ($entity_type->isRevisionable() && $field_definition->isRevisionable()) {
+ $tables[] = $table_mapping->getDedicatedRevisionTableName($field_definition);
+ }
+
+ $column_name = $field_name . '_' . $property_to_remove;
+
+ foreach ($tables as $table) {
+ if ($schema->fieldExists($table, $column_name)) {
+ $schema->dropField($table, $column_name);
}
}
+
+ // Update the tracked entity table schema.
+ $schema_key = "$entity_type_id.field_schema_data.$field_name";
+ $field_schema_data = $entity_storage_schema_sql->get($schema_key);
+ foreach ($field_schema_data as $table_name => $field_schema) {
+ // Remove the column from the field schema data.
+ unset($field_schema_data[$table_name]['fields'][$column_name]);
+ }
+ $entity_storage_schema_sql->set($schema_key, $field_schema_data);
+
+ $definitions = $last_installed_schema_repository->getLastInstalledFieldStorageDefinitions($entity_type_id);
+ $definitions[$field_name] = $field_definition;
+ $last_installed_schema_repository->setLastInstalledFieldStorageDefinitions($entity_type_id, $definitions);
}
}
}
diff --git a/tablefield.post_update.php b/tablefield.post_update.php
index c3c8e90..436b6ea 100644
--- a/tablefield.post_update.php
+++ b/tablefield.post_update.php
@@ -40,7 +40,7 @@ function tablefield_post_update_implement_tablefield_entity_view_display_schema(
->update($sandbox, 'entity_view_display', function (EntityViewDisplayInterface $entityViewDisplay) {
$updated = FALSE;
foreach ($entityViewDisplay->getComponents() as $key => $component) {
- if ($component['type'] === 'tablefield') {
+ if (isset($component['type']) && $component['type'] === 'tablefield') {
$component['settings']['row_header'] = (bool) $component['settings']['row_header'];
$component['settings']['column_header'] = (bool) $component['settings']['column_header'];
$entityViewDisplay->setComponent($key, $component);
diff --git a/tests/src/Functional/TableValueFieldTest.php b/tests/src/Functional/TableValueFieldTest.php
index 8264180..70d66a9 100644
--- a/tests/src/Functional/TableValueFieldTest.php
+++ b/tests/src/Functional/TableValueFieldTest.php
@@ -2,6 +2,7 @@
namespace Drupal\Tests\tablefield\Functional;
+use Drupal\field\Entity\FieldConfig;
use Drupal\Tests\BrowserTestBase;
/**
@@ -21,7 +22,7 @@ class TableValueFieldTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
- protected static $modules = ['node', 'tablefield'];
+ protected static $modules = ['node', 'tablefield', 'filter_test'];
/**
* {@inheritdoc}
@@ -75,4 +76,42 @@ class TableValueFieldTest extends BrowserTestBase {
$assert_session->elementContains('css', 'table#tablefield-node-1-field_table-0 tbody tr td.row_2.col_2', 'Row 2-3');
}
+ /**
+ * Create a node with with a tablefield and cell processing enabled.
+ */
+ public function testTablefieldCellProcessing() {
+ // Enable cell processing.
+ $field_config = FieldConfig::loadByName('node', 'article', 'field_table');
+ $field_config->setSetting('cell_processing', TRUE)->save();
+
+ $this->drupalGet('node/add/article');
+ $this->submitForm([
+ 'title[0][value]' => 'Llamas are cool',
+ 'field_table[0][tablefield][table][0][0][value]' => 'Bold text',
+ 'field_table[0][tablefield][table][0][0][format]' => 'filtered_html',
+ 'field_table[0][tablefield][table][0][1][value]' => 'Forbidden HTML',
+ 'field_table[0][tablefield][table][0][1][format]' => 'filtered_html',
+ 'field_table[0][tablefield][table][0][2][value]' => 'Underlined text.',
+ 'field_table[0][tablefield][table][0][2][format]' => 'full_html',
+ ], 'Save');
+
+ $assert_session = $this->assertSession();
+ $assert_session->pageTextContains('Article Llamas are cool has been created.');
+
+ // The tag is allowed by the filtered_html format, so this should
+ // be shown.
+ $assert_session->elementContains('css', 'table#tablefield-node-1-field_table-0 thead th.row_0.col_0', 'Bold text');
+ // The tag is not allowed by the filtered_html format, so this tag
+ // should be removed.
+ $assert_session->elementContains('css', 'table#tablefield-node-1-field_table-0 thead th.row_0.col_1', 'Forbidden');
+ // All HTML is allowed by the full_html format.
+ $assert_session->elementContains('css', 'table#tablefield-node-1-field_table-0 thead th.row_0.col_2', 'Underlined text.');
+
+ // Check that the submitted data is correctly saved in the database.
+ $node = $this->drupalGetNodeByTitle('Llamas are cool');
+ $this->assertEquals(['value' => 'Bold text', 'format' => 'filtered_html'], $node->get('field_table')->value[0][0]);
+ $this->assertEquals(['value' => 'Forbidden HTML', 'format' => 'filtered_html'], $node->get('field_table')->value[0][1]);
+ $this->assertEquals(['value' => 'Underlined text.', 'format' => 'full_html'], $node->get('field_table')->value[0][2]);
+ }
+
}