diff --git a/PLUGIN_TYPES.md b/PLUGIN_TYPES.md index 991f5cc..63c8ab4 100644 --- a/PLUGIN_TYPES.md +++ b/PLUGIN_TYPES.md @@ -26,8 +26,9 @@ on the class, but the default class takes the following: Defaults to "plugin.plugin_configuration.[plugin_type_id].[plugin_id]" A configuration schema MAY be provided for all configurable plugin types. If a -schema is provided, its name MUST be like +schema is provided, its name MAY be like `plugin.plugin_configuration.$plugin_type_id.$plugin_id`, where -`$plugin_type_id` is the plugin type ID as specified in +`$plugin_type_id` is the plugin type ID as specified in `$module.plugin_type.yml` and `$plugin_id` is the ID of a specific plugin, or an -asterisk (`*`) to apply to all plugins of that type. +asterisk (`*`) to apply to all plugins of that type. This is recommended over +specifying a custom ID in the plugin type definition. diff --git a/config/schema/plugin.schema.yml b/config/schema/plugin.schema.yml index 5af9b9a..cff8247 100644 --- a/config/schema/plugin.schema.yml +++ b/config/schema/plugin.schema.yml @@ -10,7 +10,10 @@ plugin.plugin_configuration.plugin_selector.plugin_selector_base: label: label: Label type: label - name: + description: + label: Label + type: label + required: label: Required type: boolean diff --git a/modules/plugin_test_helper/config/schema/plugin_test_helper.schema.yml b/modules/plugin_test_helper/config/schema/plugin_test_helper.schema.yml new file mode 100644 index 0000000..0290d57 --- /dev/null +++ b/modules/plugin_test_helper/config/schema/plugin_test_helper.schema.yml @@ -0,0 +1,9 @@ +plugin_test_helper.plugin_configuration.plugin_test_helper_mock.*: + type: ignore + +plugin_test_helper.plugin_configuration.plugin_test_helper_mock.plugin_test_helper_configurable_plugin: + type: mapping + mapping: + foo: + label: A random string + type: string diff --git a/modules/plugin_test_helper/plugin_test_helper.plugin_type.yml b/modules/plugin_test_helper/plugin_test_helper.plugin_type.yml index 8dc7dc0..82bbe3b 100644 --- a/modules/plugin_test_helper/plugin_test_helper.plugin_type.yml +++ b/modules/plugin_test_helper/plugin_test_helper.plugin_type.yml @@ -1,3 +1,5 @@ plugin_test_helper_mock: label: Mock plugin plugin_manager_service_id: plugin.manager.plugin_test_helper.mock + plugin_definition_decorator_class: \Drupal\plugin\PluginDefinition\ArrayPluginDefinitionDecorator + plugin_configuration_schema_id: plugin_test_helper.plugin_configuration.plugin_test_helper_mock.[plugin_id] diff --git a/modules/plugin_test_helper/src/Plugin/PluginTestHelper/MockManager.php b/modules/plugin_test_helper/src/Plugin/PluginTestHelper/MockManager.php index 91d8f46..e98aad1 100644 --- a/modules/plugin_test_helper/src/Plugin/PluginTestHelper/MockManager.php +++ b/modules/plugin_test_helper/src/Plugin/PluginTestHelper/MockManager.php @@ -15,6 +15,10 @@ use Drupal\plugin\PluginDiscovery\TypedDiscoveryInterface; /** * Provides a plugin manager for testing plugin-related functionality. + * + * Configuration schemas for this manager's plugins are named + * "plugin_test_helper.plugin_configuration.plugin_test_helper_mock.[plugin_id]", + * where "[plugin_id]" is the ID of the plugin the schema is for. */ class MockManager extends PluginManagerBase implements TypedDiscoveryInterface { diff --git a/plugin.install b/plugin.install new file mode 100644 index 0000000..c2271d3 --- /dev/null +++ b/plugin.install @@ -0,0 +1,37 @@ +listAll('field.field.'); + foreach ($plugin_field_names as $plugin_field_name) { + $config = $config_factory->getEditable($plugin_field_name); + if (strpos($config->get('field_type'), 'plugin:') === 0) { + $plugin_type_id = explode(':', $config->get('field_type'))[1]; + $plugin_type = $plugin_type_manager->getPluginType($plugin_type_id); + $default_values = $config->get('default_value'); + foreach ($default_values as &$default_value) { + if ($plugin_type instanceof ConfigurablePluginTypeInterface) { + $default_value['plugin_configuration_schema_id'] = $plugin_type->getPluginConfigurationSchemaId($default_value['plugin_id']); + } + else { + $default_value['plugin_configuration_schema_id'] = sprintf('plugin.plugin_configuration.%s.%s', $default_value['plugin_type_id'], $default_value['plugin_id']); + } + unset($default_value['plugin_type_id']); + $config->set('default_value', $default_values); + } + $config->save(); + } + } +} diff --git a/plugin.plugin_type.yml b/plugin.plugin_type.yml index 7dc8d9a..fa9404c 100644 --- a/plugin.plugin_type.yml +++ b/plugin.plugin_type.yml @@ -4,12 +4,6 @@ plugin_selector: plugin_definition_decorator_class: \Drupal\plugin\PluginDefinition\ArrayPluginDefinitionDecorator # Drupal core's plugin types. -block: - label: Block - provider: block - plugin_manager_service_id: plugin.manager.block - plugin_definition_decorator_class: \Drupal\plugin\PluginDefinition\BlockPluginDefinitionDecorator - plugin_configuration_schema_id: block.settings.[plugin_id] entity_reference_selector: label: Entity reference selector provider: core @@ -105,6 +99,14 @@ aggregator_processor: plugin_manager_service_id: plugin.manager.aggregator.processor plugin_definition_decorator_class: \Drupal\plugin\PluginDefinition\ArrayPluginDefinitionDecorator +# Block's plugin types. +block: + label: Block + provider: block + plugin_manager_service_id: plugin.manager.block + plugin_definition_decorator_class: \Drupal\plugin\PluginDefinition\BlockPluginDefinitionDecorator + plugin_configuration_schema_id: block.settings.[plugin_id] + # CKEditor's plugin types. ckeditor_plugin: label: CKEditor plugin diff --git a/src/Plugin/Field/FieldType/PluginCollectionItemBase.php b/src/Plugin/Field/FieldType/PluginCollectionItemBase.php index 8a3f00d..c12160d 100644 --- a/src/Plugin/Field/FieldType/PluginCollectionItemBase.php +++ b/src/Plugin/Field/FieldType/PluginCollectionItemBase.php @@ -13,6 +13,7 @@ use Drupal\Core\Field\FieldItemBase; use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Core\TypedData\DataDefinition; use Drupal\Core\TypedData\MapDataDefinition; +use Drupal\plugin\PluginType\ConfigurablePluginTypeInterface; /** * Provides a base for plugin collection field items. @@ -178,10 +179,11 @@ abstract class PluginCollectionItemBase extends FieldItemBase implements PluginC // but we can only represent this item's value using the plugin instance's // ID and configuration. parent::getValue() skips computed properties, so we // must return them here. + $plugin_type = $this->getPluginType(); return [ 'plugin_id' => $this->get('plugin_id')->getValue(), 'plugin_configuration' => $this->get('plugin_configuration')->getValue(), - 'plugin_configuration_schema_id' => $this->getPluginType()->getPluginConfigurationSchemaId($this->get('plugin_id')->getValue()), + 'plugin_configuration_schema_id' => $plugin_type instanceof ConfigurablePluginTypeInterface ? $plugin_type->getPluginConfigurationSchemaId($this->get('plugin_id')->getValue()) : 'plugin.plugin_configuration.*.*', ]; } diff --git a/src/Tests/HookUpdateNTest.php b/src/Tests/HookUpdateNTest.php new file mode 100644 index 0000000..5c8f44b --- /dev/null +++ b/src/Tests/HookUpdateNTest.php @@ -0,0 +1,72 @@ +configFactory = $this->container->get('config.factory'); + } + + /** + * {@inheritdoc} + */ + protected function setDatabaseDumpFiles() { + $this->databaseDumpFiles = [ + realpath(DRUPAL_ROOT . '/core/modules/system/tests/fixtures/update/drupal-8.bare.standard.php.gz'), + realpath(__DIR__ . '/../../tests/fixtures/module_installation/database_dump.php'), + realpath(__DIR__ . '/../../tests/fixtures/plugin_update_8001/database_dump.php'), + ]; + } + + /** + * Tests plugin_update_8001(). + * + * @see plugin_update_8001() + */ + public function testPluginUpdate8001() { + $this->runUpdates(); + + // Test the ingegrity of the plugin selector fields. + /** @var string[] $fields Keys are config names, and values are matching plugin configuration schema IDs */ + $fields = [ + 'field.field.user.user.field_plugin_selector' => 'plugin.plugin_configuration.plugin_selector.plugin_select_list', + 'field.field.user.user.field_plugin_test_helper_mock' => 'plugin_test_helper.plugin_configuration.plugin_test_helper_mock.plugin_test_helper_configurable_plugin', + ]; + foreach ($fields as $config_name => $plugin_config_schema_id) { + $config = $this->configFactory->get($config_name)->get(); + foreach ($config['default_value'] as $default_value) { + // Confirm that the "plugin_type_id" property has been removed. + $this->assertFalse(array_key_exists('plugin_type_id', $default_value)); + // Confirm that the "plugin_configuration_schema_id" property exists and + // has been populated. + $this->assertTrue(array_key_exists('plugin_configuration_schema_id', $default_value)); + $this->assertEqual($default_value['plugin_configuration_schema_id'], $plugin_config_schema_id); + } + } + } + +} diff --git a/tests/fixtures/module_installation/config/schema/plugin.schema.yml b/tests/fixtures/module_installation/config/schema/plugin.schema.yml new file mode 100644 index 0000000..5dbea13 --- /dev/null +++ b/tests/fixtures/module_installation/config/schema/plugin.schema.yml @@ -0,0 +1,43 @@ +plugin.plugin_configuration.plugin_selector.plugin_selector_base: + type: mapping + mapping: + collect_plugin_configuration: + label: Collect plugin configuration + type: boolean + keep_previously_selected_plugins: + label: Keep previously selected plugins + type: boolean + label: + label: Label + type: label + description: + label: Label + type: label + required: + label: Required + type: boolean + +plugin.plugin_configuration.plugin_selector.plugin_radios: + type: plugin.plugin_configuration.plugin_selector.plugin_selector_base + +plugin.plugin_configuration.plugin_selector.plugin_select_list: + type: plugin.plugin_configuration.plugin_selector.plugin_selector_base + +# Fallback plugin configuration in case no more specific schemas are available. +plugin.plugin_configuration.*: + type: ignore + +# The "plugin" field type schema. +"field.value.plugin:*": + label: Plugin collection field value + mapping: + plugin_type_id: + label: Plugin type ID + type: string + plugin_id: + label: Plugin ID + type: string + plugin_configuration: + label: Plugin configuration + type: plugin.plugin_configuration.[%parent.plugin_type_id].[%parent.plugin_id]: + type: config_object diff --git a/tests/fixtures/module_installation/database_dump.php b/tests/fixtures/module_installation/database_dump.php new file mode 100644 index 0000000..48c4b33 --- /dev/null +++ b/tests/fixtures/module_installation/database_dump.php @@ -0,0 +1,45 @@ +insert('key_value') + ->fields([ + 'collection' => 'system.schema', + 'name' => 'plugin', + 'value' => 'i:8000;', + ]) + ->execute(); +$connection->insert('key_value') + ->fields([ + 'collection' => 'system.schema', + 'name' => 'plugin_test_helper', + 'value' => 'i:8000;', + ]) + ->execute(); + +// Update core.extension. +$extensions = $connection->select('config') + ->fields('config', ['data']) + ->condition('collection', '') + ->condition('name', 'core.extension') + ->execute() + ->fetchField(); +$extensions = unserialize($extensions); +$extensions['module']['plugin'] = 8000; +$extensions['module']['plugin_test_helper'] = 8000; +$connection->update('config') + ->fields([ + 'data' => serialize($extensions), + ]) + ->condition('collection', '') + ->condition('name', 'core.extension') + ->execute(); diff --git a/tests/fixtures/plugin_update_8001/config/field.field.user.user.field_plugin_selector.yml b/tests/fixtures/plugin_update_8001/config/field.field.user.user.field_plugin_selector.yml new file mode 100644 index 0000000..d9acb02 --- /dev/null +++ b/tests/fixtures/plugin_update_8001/config/field.field.user.user.field_plugin_selector.yml @@ -0,0 +1,30 @@ +uuid: 7ce5d8e1-9909-490d-9d01-1d3e5035a6a5 +langcode: en +status: true +dependencies: + config: + - field.storage.user.field_plugin_selector + module: + - plugin + - user +id: user.user.field_plugin_selector +field_name: field_plugin_selector +entity_type: user +bundle: user +label: 'Plugin selector' +description: '' +required: false +translatable: false +default_value: + - + plugin_type_id: plugin_selector + plugin_id: plugin_select_list + plugin_configuration: + description: null + label: null + required: false + collect_plugin_configuration: true + keep_previously_selected_plugins: true +default_value_callback: '' +settings: { } +field_type: 'plugin:plugin_selector' diff --git a/tests/fixtures/plugin_update_8001/config/field.field.user.user.field_plugin_test_helper_mock.yml b/tests/fixtures/plugin_update_8001/config/field.field.user.user.field_plugin_test_helper_mock.yml new file mode 100644 index 0000000..71590ba --- /dev/null +++ b/tests/fixtures/plugin_update_8001/config/field.field.user.user.field_plugin_test_helper_mock.yml @@ -0,0 +1,26 @@ +uuid: 8f056b03-c49e-4912-9500-e8e6c91e0358 +langcode: en +status: true +dependencies: + config: + - field.storage.user.field_plugin_test_helper_mock + module: + - plugin + - user +id: user.user.field_plugin_test_helper_mock +field_name: field_plugin_test_helper_mock +entity_type: user +bundle: user +label: 'Mock plugin' +description: '' +required: false +translatable: false +default_value: + - + plugin_type_id: plugin_test_helper_mock + plugin_id: plugin_test_helper_configurable_plugin + plugin_configuration: + foo: bar +default_value_callback: '' +settings: { } +field_type: 'plugin:plugin_test_helper_mock' diff --git a/tests/fixtures/plugin_update_8001/config/field.storage.user.field_plugin_selector.yml b/tests/fixtures/plugin_update_8001/config/field.storage.user.field_plugin_selector.yml new file mode 100644 index 0000000..4923cdd --- /dev/null +++ b/tests/fixtures/plugin_update_8001/config/field.storage.user.field_plugin_selector.yml @@ -0,0 +1,19 @@ +uuid: 798b1d11-8c50-43f7-9b1d-291ebc218eb5 +langcode: en +status: true +dependencies: + module: + - plugin + - user +id: user.field_plugin_selector +field_name: field_plugin_selector +entity_type: user +type: 'plugin:plugin_selector' +settings: { } +module: plugin +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/tests/fixtures/plugin_update_8001/config/field.storage.user.field_plugin_test_helper_mock.yml b/tests/fixtures/plugin_update_8001/config/field.storage.user.field_plugin_test_helper_mock.yml new file mode 100644 index 0000000..ff0840e --- /dev/null +++ b/tests/fixtures/plugin_update_8001/config/field.storage.user.field_plugin_test_helper_mock.yml @@ -0,0 +1,19 @@ +uuid: 68f6406e-960d-4aa1-b503-3d2b3a890d16 +langcode: en +status: true +dependencies: + module: + - plugin + - user +id: user.field_plugin_test_helper_mock +field_name: field_plugin_test_helper_mock +entity_type: user +type: 'plugin:plugin_test_helper_mock' +settings: { } +module: plugin +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/tests/fixtures/plugin_update_8001/database_dump.php b/tests/fixtures/plugin_update_8001/database_dump.php new file mode 100644 index 0000000..f0793fe --- /dev/null +++ b/tests/fixtures/plugin_update_8001/database_dump.php @@ -0,0 +1,238 @@ +insert('config') + ->fields([ + 'collection', + 'name', + 'data', + ]) + ->values([ + 'collection' => '', + 'name' => 'field.storage.' . $config['id'], + 'data' => serialize($config), + ]) + ->execute(); +// We need to Update the registry of "last installed" field definitions. +$installed = $connection->select('key_value') + ->fields('key_value', ['value']) + ->condition('collection', 'entity.definitions.installed') + ->condition('name', 'user.field_storage_definitions') + ->execute() + ->fetchField(); +$installed = unserialize($installed); +$installed['field_plugin_selector'] = new FieldStorageConfig($config); +$connection->update('key_value') + ->condition('collection', 'entity.definitions.installed') + ->condition('name', 'user.field_storage_definitions') + ->fields([ + 'value' => serialize($installed) + ]) + ->execute(); + +// Create the "Plugin selector" plugin field. +$config = Yaml::decode(file_get_contents(__DIR__ . '/config/field.field.user.user.field_plugin_selector.yml')); +$connection->insert('config') + ->fields([ + 'collection', + 'name', + 'data', + ]) + ->values([ + 'collection' => '', + 'name' => 'field.field.' . $config['id'], + 'data' => serialize($config), + ]) + ->execute(); + +// Create the table for the "Plugin selector" plugin field's values. +$connection->schema()->createTable('user__field_plugin_selector', array( + 'fields' => array( + 'bundle' => array( + 'type' => 'varchar_ascii', + 'not null' => TRUE, + 'length' => '128', + 'default' => '', + ), + 'deleted' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'tiny', + 'default' => '0', + ), + 'entity_id' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'revision_id' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'langcode' => array( + 'type' => 'varchar_ascii', + 'not null' => TRUE, + 'length' => '32', + 'default' => '', + ), + 'delta' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'field_plugin_selector_plugin_id' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '255', + ), + 'field_plugin_selector_plugin_configuration' => array( + 'type' => 'blob', + 'not null' => FALSE, + 'size' => 'normal', + ), + ), + 'primary key' => array( + 'entity_id', + 'deleted', + 'delta', + 'langcode', + ), + 'indexes' => array( + 'bundle' => array( + 'bundle', + ), + 'revision_id' => array( + 'revision_id', + ), + ), + 'mysql_character_set' => 'utf8mb4', +)); + +// Create the "Mock plugin" plugin field storage. +$config = Yaml::decode(file_get_contents(__DIR__ . '/config/field.storage.user.field_plugin_test_helper_mock.yml')); +$connection->insert('config') + ->fields([ + 'collection', + 'name', + 'data', + ]) + ->values([ + 'collection' => '', + 'name' => 'field.storage.' . $config['id'], + 'data' => serialize($config), + ]) + ->execute(); +// We need to Update the registry of "last installed" field definitions. +$installed = $connection->select('key_value') + ->fields('key_value', ['value']) + ->condition('collection', 'entity.definitions.installed') + ->condition('name', 'user.field_storage_definitions') + ->execute() + ->fetchField(); +$installed = unserialize($installed); +$installed['field_plugin_test_helper_mock'] = new FieldStorageConfig($config); +$connection->update('key_value') + ->condition('collection', 'entity.definitions.installed') + ->condition('name', 'user.field_storage_definitions') + ->fields([ + 'value' => serialize($installed) + ]) + ->execute(); + +// Create the "Mock plugin" plugin field. +$config = Yaml::decode(file_get_contents(__DIR__ . '/config/field.field.user.user.field_plugin_test_helper_mock.yml')); +$connection->insert('config') + ->fields([ + 'collection', + 'name', + 'data', + ]) + ->values([ + 'collection' => '', + 'name' => 'field.field.' . $config['id'], + 'data' => serialize($config), + ]) + ->execute(); + +// Create the table for the "Mock plugin" plugin field's values. +$connection->schema()->createTable('user__field_plugin_test_helper_mock', array( + 'fields' => array( + 'bundle' => array( + 'type' => 'varchar_ascii', + 'not null' => TRUE, + 'length' => '128', + 'default' => '', + ), + 'deleted' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'tiny', + 'default' => '0', + ), + 'entity_id' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'revision_id' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'langcode' => array( + 'type' => 'varchar_ascii', + 'not null' => TRUE, + 'length' => '32', + 'default' => '', + ), + 'delta' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'field_plugin_test_helper_mock_plugin_id' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '255', + ), + 'field_plugin_test_helper_mock_plugin_configuration' => array( + 'type' => 'blob', + 'not null' => FALSE, + 'size' => 'normal', + ), + ), + 'primary key' => array( + 'entity_id', + 'deleted', + 'delta', + 'langcode', + ), + 'indexes' => array( + 'bundle' => array( + 'bundle', + ), + 'revision_id' => array( + 'revision_id', + ), + ), + 'mysql_character_set' => 'utf8mb4', +));