Issue #2432791 by alexpott, vijaycs85, tim.plunkett, joshtaylor, Fabianx, Berdir, yched, bojanz: Skip Config::save schema validation of config data for trusted data.

8.0.x
Nathaniel Catchpole 2015-05-05 11:47:16 +01:00
parent f8f024e3aa
commit e0aae8c26a
82 changed files with 664 additions and 115 deletions

View File

@ -1215,8 +1215,9 @@ function file_directory_temp() {
// everything to use slash which is supported on all platforms.
$temporary_directory = str_replace('\\', '/', $temporary_directory);
}
// Save the path of the discovered directory.
$config->set('path.temporary', $temporary_directory)->save();
// Save the path of the discovered directory. Do not check config schema on
// save.
$config->set('path.temporary', (string) $temporary_directory)->save(TRUE);
}
return $temporary_directory;

View File

@ -619,9 +619,9 @@ function drupal_install_system($install_state) {
// Ensure default language is saved.
if (isset($install_state['parameters']['langcode'])) {
\Drupal::configFactory()->getEditable('system.site')
->set('langcode', $install_state['parameters']['langcode'])
->set('default_langcode', $install_state['parameters']['langcode'])
->save();
->set('langcode', (string) $install_state['parameters']['langcode'])
->set('default_langcode', (string) $install_state['parameters']['langcode'])
->save(TRUE);
}
}

View File

@ -179,10 +179,12 @@ function drupal_required_modules() {
function module_set_weight($module, $weight) {
$extension_config = \Drupal::configFactory()->getEditable('core.extension');
if ($extension_config->get("module.$module") !== NULL) {
// Pre-cast the $weight to an integer so that we can save this without using
// schema. This is a performance improvement for module installation.
$extension_config
->set("module.$module", $weight)
->set("module.$module", (int) $weight)
->set('module', module_config_sort($extension_config->get('module')))
->save();
->save(TRUE);
// Prepare the new module list, sorted by weight, including filenames.
// @see \Drupal\Core\Extension\ModuleHandler::install()

View File

@ -203,22 +203,24 @@ class Config extends StorableConfigBase {
/**
* {@inheritdoc}
*/
public function save() {
public function save($has_trusted_data = FALSE) {
// Validate the configuration object name before saving.
static::validateName($this->name);
// If there is a schema for this configuration object, cast all values to
// conform to the schema.
if ($this->typedConfigManager->hasConfigSchema($this->name)) {
// Ensure that the schema wrapper has the latest data.
$this->schemaWrapper = NULL;
foreach ($this->data as $key => $value) {
$this->data[$key] = $this->castValue($key, $value);
if (!$has_trusted_data) {
if ($this->typedConfigManager->hasConfigSchema($this->name)) {
// Ensure that the schema wrapper has the latest data.
$this->schemaWrapper = NULL;
foreach ($this->data as $key => $value) {
$this->data[$key] = $this->castValue($key, $value);
}
}
}
else {
foreach ($this->data as $key => $value) {
$this->validateValue($key, $value);
else {
foreach ($this->data as $key => $value) {
$this->validateValue($key, $value);
}
}
}
@ -229,6 +231,9 @@ class Config extends StorableConfigBase {
$this->isNew = FALSE;
$this->eventDispatcher->dispatch(ConfigEvents::SAVE, new ConfigCrudEvent($this));
$this->originalData = $this->data;
// Potentially configuration schema could have changed the underlying data's
// types.
$this->resetOverriddenData();
return $this;
}
@ -302,4 +307,5 @@ class Config extends StorableConfigBase {
}
}
}
}

View File

@ -304,11 +304,11 @@ class ConfigInstaller implements ConfigInstallerInterface {
$entity = $entity_storage->createFromStorageRecord($new_config->get());
}
if ($entity->isInstallable()) {
$entity->save();
$entity->trustData()->save();
}
}
else {
$new_config->save();
$new_config->save(TRUE);
}
}
}

View File

@ -105,6 +105,13 @@ abstract class ConfigEntityBase extends Entity implements ConfigEntityInterface
*/
protected $third_party_settings = array();
/**
* Trust supplied data and not use configuration schema on save.
*
* @var bool
*/
protected $trustedData = FALSE;
/**
* Overrides Entity::__construct().
*/
@ -265,22 +272,31 @@ abstract class ConfigEntityBase extends Entity implements ConfigEntityInterface
*/
public function toArray() {
$properties = array();
$config_name = $this->getEntityType()->getConfigPrefix() . '.' . $this->id();
$definition = $this->getTypedConfig()->getDefinition($config_name);
if (!isset($definition['mapping'])) {
throw new SchemaIncompleteException(SafeMarkup::format('Incomplete or missing schema for @config_name', array('@config_name' => $config_name)));
/** @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $entity_type */
$entity_type = $this->getEntityType();
$properties_to_export = $entity_type->getPropertiesToExport();
if (empty($properties_to_export)) {
$config_name = $entity_type->getConfigPrefix() . '.' . $this->id();
$definition = $this->getTypedConfig()->getDefinition($config_name);
if (!isset($definition['mapping'])) {
throw new SchemaIncompleteException(SafeMarkup::format('Incomplete or missing schema for @config_name', array('@config_name' => $config_name)));
}
$properties_to_export = array_combine(array_keys($definition['mapping']), array_keys($definition['mapping']));
}
$id_key = $this->getEntityType()->getKey('id');
foreach (array_keys($definition['mapping']) as $name) {
$id_key = $entity_type->getKey('id');
foreach ($properties_to_export as $property_name => $export_name) {
// Special handling for IDs so that computed compound IDs work.
// @see \Drupal\Core\Entity\EntityDisplayBase::id()
if ($name == $id_key) {
$properties[$name] = $this->id();
if ($property_name == $id_key) {
$properties[$export_name] = $this->id();
}
else {
$properties[$name] = $this->get($name);
$properties[$export_name] = $this->get($property_name);
}
}
if (empty($this->third_party_settings)) {
unset($properties['third_party_settings']);
}
@ -328,7 +344,7 @@ abstract class ConfigEntityBase extends Entity implements ConfigEntityInterface
throw new ConfigDuplicateUUIDException(SafeMarkup::format('Attempt to save a configuration entity %id with UUID %uuid when this entity already exists with UUID %original_uuid', array('%id' => $this->id(), '%uuid' => $this->uuid(), '%original_uuid' => $original->uuid())));
}
}
if (!$this->isSyncing()) {
if (!$this->isSyncing() && !$this->trustedData) {
// Ensure the correct dependencies are present. If the configuration is
// being written during a configuration synchronization then there is no
// need to recalculate the dependencies.
@ -572,4 +588,28 @@ abstract class ConfigEntityBase extends Entity implements ConfigEntityInterface
return TRUE;
}
/**
* {@inheritdoc}
*/
public function trustData() {
$this->trustedData = TRUE;
return $this;
}
/**
* {@inheritdoc}
*/
public function hasTrustedData() {
return $this->trustedData;
}
/**
* {@inheritdoc}
*/
public function save() {
$return = parent::save();
$this->trustedData = FALSE;
return $return;
}
}

View File

@ -203,4 +203,27 @@ interface ConfigEntityInterface extends EntityInterface, ThirdPartySettingsInter
*/
public function isInstallable();
/**
* Sets that the data should be trusted.
*
* If the data is trusted then dependencies will not be calculated on save and
* schema will not be used to cast the values. Generally this is only used
* during module and theme installation. Once the config entity has been saved
* the data will no longer be marked as trusted. This is an optimization for
* creation of configuration during installation.
*
* @return $this
*
* @see \Drupal\Core\Config\ConfigInstaller::createConfiguration()
*/
public function trustData();
/**
* Gets whether on not the data is trusted.
*
* @return bool
* TRUE if the configuration data is trusted, FALSE if not.
*/
public function hasTrustedData();
}

View File

@ -256,7 +256,19 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora
// Retrieve the desired properties and set them in config.
$config->setData($this->mapToStorageRecord($entity));
$config->save();
$config->save($entity->hasTrustedData());
// Update the entity with the values stored in configuration. It is possible
// that configuration schema has casted some of the values.
if (!$entity->hasTrustedData()) {
$data = $this->mapFromStorageRecords(array($config->get()));
$updated_entity = current($data);
foreach (array_keys($config->get()) as $property) {
$value = $updated_entity->get($property);
$entity->set($property, $value);
}
}
return $is_new ? SAVED_NEW : SAVED_UPDATED;
}

View File

@ -34,6 +34,22 @@ class ConfigEntityType extends EntityType implements ConfigEntityTypeInterface {
*/
protected $static_cache = FALSE;
/**
* The list of configuration entity properties to export from the annotation.
*
* @var array
*/
protected $config_export = [];
/**
* The result of merging config_export annotation with the defaults.
*
* This is stored on the class so that it does not have to be recalculated.
*
* @var array
*/
protected $mergedConfigExport = [];
/**
* {@inheritdoc}
*
@ -146,4 +162,32 @@ class ConfigEntityType extends EntityType implements ConfigEntityTypeInterface {
}
}
/**
* {@inheritdoc}
*/
public function getPropertiesToExport() {
if (!empty($this->config_export)) {
if (empty($this->mergedConfigExport)) {
// Always add default properties to be exported.
$this->mergedConfigExport = [
'uuid' => 'uuid',
'langcode' => 'langcode',
'status' => 'status',
'dependencies' => 'dependencies',
'third_party_settings' => 'third_party_settings',
];
foreach ($this->config_export as $property => $name) {
if (is_numeric($property)) {
$this->mergedConfigExport[$name] = $name;
}
else {
$this->mergedConfigExport[$property] = $name;
}
}
}
return $this->mergedConfigExport;
}
return NULL;
}
}

View File

@ -61,4 +61,13 @@ interface ConfigEntityTypeInterface extends EntityTypeInterface {
*/
public function getConfigPrefix();
/**
* Gets the config entity properties to export if declared on the annotation.
*
* @return array|NULL
* The properties to export or NULL if they can not be determine from the
* config entity type annotation.
*/
public function getPropertiesToExport();
}

View File

@ -44,7 +44,7 @@ class ImmutableConfig extends Config {
/**
* {@inheritdoc}
*/
public function save() {
public function save($has_trusted_data = FALSE) {
throw new ImmutableConfigException(SafeMarkup::format('Can not save immutable configuration !name. Use \Drupal\Core\Config\ConfigFactoryInterface::getEditable() to retrieve a mutable configuration object', ['!name' => $this->getName()]));
}

View File

@ -66,11 +66,18 @@ abstract class StorableConfigBase extends ConfigBase {
/**
* Saves the configuration object.
*
* @param bool $has_trusted_data
* Set to TRUE is the configuration data has already been checked to ensure
* it conforms to schema. Generally this is only used during module and
* theme installation.
*
* Must invalidate the cache tags associated with the configuration object.
*
* @return $this
*
* @see \Drupal\Core\Config\ConfigInstaller::createConfiguration()
*/
abstract public function save();
abstract public function save($has_trusted_data = FALSE);
/**
* Deletes the configuration object.

View File

@ -25,7 +25,13 @@ use Drupal\Core\Datetime\DateFormatInterface;
* "label" = "label"
* },
* admin_permission = "administer site configuration",
* list_cache_tags = { "rendered" }
* list_cache_tags = { "rendered" },
* config_export = {
* "id",
* "label",
* "locked",
* "pattern",
* }
* )
*/
class DateFormat extends ConfigEntityBase implements DateFormatInterface {

View File

@ -23,6 +23,14 @@ use Drupal\Core\Form\FormStateInterface;
* entity_keys = {
* "id" = "id",
* "status" = "status"
* },
* config_export = {
* "id",
* "targetEntityType",
* "bundle",
* "mode",
* "content",
* "hidden",
* }
* )
*/

View File

@ -33,6 +33,12 @@ use Drupal\Core\Entity\EntityFormModeInterface;
* entity_keys = {
* "id" = "id",
* "label" = "label"
* },
* config_export = {
* "id",
* "label",
* "targetEntityType",
* "cache",
* }
* )
*/

View File

@ -24,6 +24,14 @@ use Drupal\Core\Entity\EntityDisplayBase;
* entity_keys = {
* "id" = "id",
* "status" = "status"
* },
* config_export = {
* "id",
* "targetEntityType",
* "bundle",
* "mode",
* "content",
* "hidden",
* }
* )
*/

View File

@ -35,6 +35,12 @@ use Drupal\Core\Entity\EntityViewModeInterface;
* entity_keys = {
* "id" = "id",
* "label" = "label"
* },
* config_export = {
* "id",
* "label",
* "targetEntityType",
* "cache",
* }
* )
*/

View File

@ -155,10 +155,12 @@ class ModuleInstaller implements ModuleInstallerInterface {
// exceptions if the configuration is not valid.
$config_installer->checkConfigurationToInstall('module', $module);
// Save this data without checking schema. This is a performance
// improvement for module installation.
$extension_config
->set("module.$module", 0)
->set('module', module_config_sort($extension_config->get('module')))
->save();
->save(TRUE);
// Prepare the new module list, sorted by weight, including filenames.
// This list is used for both the ModuleHandler and DrupalKernel. It
@ -385,8 +387,9 @@ class ModuleInstaller implements ModuleInstallerInterface {
// Remove the schema.
drupal_uninstall_schema($module);
// Remove the module's entry from the config.
\Drupal::configFactory()->getEditable('core.extension')->clear("module.$module")->save();
// Remove the module's entry from the config. Don't check schema when
// uninstalling a module since we are only clearing a key.
\Drupal::configFactory()->getEditable('core.extension')->clear("module.$module")->save(TRUE);
// Update the module handler to remove the module.
// The current ModuleHandler instance is obsolete with the kernel rebuild

View File

@ -260,10 +260,11 @@ class ThemeHandler implements ThemeHandlerInterface {
// configuration then stop installing.
$this->configInstaller->checkConfigurationToInstall('theme', $key);
// The value is not used; the weight is ignored for themes currently.
// The value is not used; the weight is ignored for themes currently. Do
// not check schema when saving the configuration.
$extension_config
->set("theme.$key", 0)
->save();
->save(TRUE);
// Add the theme to the current list.
// @todo Remove all code that relies on $status property.
@ -358,7 +359,9 @@ class ThemeHandler implements ThemeHandlerInterface {
$this->configManager->uninstall('theme', $key);
}
$extension_config->save();
// Don't check schema when uninstalling a theme since we are only clearing
// keys.
$extension_config->save(TRUE);
$this->state->set('system.theme.data', $current_theme_data);
$this->resetSystem();

View File

@ -28,6 +28,20 @@ use Drupal\Core\Field\FieldException;
* entity_keys = {
* "id" = "id",
* "label" = "label"
* },
* config_export = {
* "id",
* "field_name",
* "entity_type",
* "bundle",
* "label",
* "description",
* "required",
* "translatable",
* "default_value",
* "default_value_callback",
* "settings",
* "field_type",
* }
* )
*/

View File

@ -260,14 +260,14 @@ class SiteConfigureForm extends ConfigFormBase {
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->config('system.site')
->set('name', $form_state->getValue('site_name'))
->set('mail', $form_state->getValue('site_mail'))
->save();
->set('name', (string) $form_state->getValue('site_name'))
->set('mail', (string) $form_state->getValue('site_mail'))
->save(TRUE);
$this->config('system.date')
->set('timezone.default', $form_state->getValue('date_default_timezone'))
->set('country.default', $form_state->getValue('site_default_country'))
->save();
->set('timezone.default', (string) $form_state->getValue('date_default_timezone'))
->set('country.default', (string) $form_state->getValue('site_default_country'))
->save(TRUE);
$account_values = $form_state->getValue('account');
@ -281,7 +281,7 @@ class SiteConfigureForm extends ConfigFormBase {
if ($update_status_module[2]) {
// Reset the configuration factory so it is updated with the new module.
$this->resetConfigFactory();
$this->config('update.settings')->set('notification.emails', array($account_values['mail']))->save();
$this->config('update.settings')->set('notification.emails', array($account_values['mail']))->save(TRUE);
}
}

View File

@ -130,20 +130,29 @@ class StaticMenuLinkOverrides implements StaticMenuLinkOverridesInterface {
public function saveOverride($id, array $definition) {
// Only allow to override a specific subset of the keys.
$expected = array(
'menu_name' => 1,
'parent' => 1,
'weight' => 1,
'expanded' => 1,
'enabled' => 1,
'menu_name' => '',
'parent' => '',
'weight' => 0,
'expanded' => FALSE,
'enabled' => FALSE,
);
// Filter the overrides to only those that are expected.
$definition = array_intersect_key($definition, $expected);
// Ensure all values are set.
$definition = $definition + $expected;
if ($definition) {
// Cast keys to avoid config schema during save.
$definition['menu_name'] = (string) $definition['menu_name'];
$definition['parent'] = (string) $definition['parent'];
$definition['weight'] = (int) $definition['weight'];
$definition['expanded'] = (bool) $definition['expanded'];
$definition['enabled'] = (bool) $definition['enabled'];
$id = static::encodeId($id);
$all_overrides = $this->getConfig()->get('definitions');
// Combine with any existing data.
$all_overrides[$id] = $definition + $this->loadOverride($id);
$this->getConfig()->set('definitions', $all_overrides)->save();
$this->getConfig()->set('definitions', $all_overrides)->save(TRUE);
}
return array_keys($definition);
}

View File

@ -38,6 +38,16 @@ use Drupal\Core\Entity\EntityStorageInterface;
* links = {
* "delete-form" = "/admin/structure/block/manage/{block}/delete",
* "edit-form" = "/admin/structure/block/manage/{block}"
* },
* config_export = {
* "id",
* "theme",
* "region",
* "weight",
* "provider",
* "plugin",
* "settings",
* "visibility",
* }
* )
*/

View File

@ -38,6 +38,12 @@ use Drupal\block_content\BlockContentTypeInterface;
* "delete-form" = "/admin/structure/block/block-content/manage/{block_content_type}/delete",
* "edit-form" = "/admin/structure/block/block-content/manage/{block_content_type}",
* "collection" = "/admin/structure/block/block-content/types",
* },
* config_export = {
* "id",
* "label",
* "revision",
* "description",
* }
* )
*/

View File

@ -1,6 +1,8 @@
langcode: en
status: true
dependencies:
module:
- book
enforced:
module:
- book

View File

@ -38,6 +38,12 @@ use Drupal\comment\CommentTypeInterface;
* "edit-form" = "/admin/structure/comment/manage/{comment_type}",
* "add-form" = "/admin/structure/comment/types/add",
* "collection" = "/admin/structure/comment/types",
* },
* config_export = {
* "id",
* "label",
* "target_entity_type_id",
* "description",
* }
* )
*/

View File

@ -275,6 +275,14 @@ class ConfigCRUDTest extends KernelTestBase {
$this->assertIdentical($config->get(), $data);
$this->assertIdentical($storage->read($name), $data);
// Test that schema type enforcement can be overridden by trusting the data.
$this->assertIdentical(99, $config->get('int'));
$config->set('int', '99')->save(TRUE);
$this->assertIdentical('99', $config->get('int'));
// Test that re-saving without testing the data enforces the schema type.
$config->save();
$this->assertIdentical($data, $config->get());
// Test that setting an unsupported type for a config object with a schema
// fails.
try {

View File

@ -17,6 +17,15 @@ use Drupal\simpletest\KernelTestBase;
*/
class ConfigEntityUnitTest extends KernelTestBase {
/**
* Exempt from strict schema checking.
*
* @see \Drupal\Core\Config\Testing\ConfigSchemaChecker
*
* @var bool
*/
protected $strictConfigSchema = FALSE;
/**
* Modules to enable.
*
@ -89,6 +98,20 @@ class ConfigEntityUnitTest extends KernelTestBase {
foreach ($entities as $entity) {
$this->assertIdentical($entity->get('style'), $style, 'The loaded entity has the correct style value specified.');
}
// Test that schema type enforcement can be overridden by trusting the data.
$entity = $this->storage->create(array(
'id' => $this->randomMachineName(),
'label' => $this->randomString(),
'style' => 999
));
$entity->save();
$this->assertIdentical('999', $entity->style);
$entity->style = 999;
$entity->trustData()->save();
$this->assertIdentical(999, $entity->style);
$entity->save();
$this->assertIdentical('999', $entity->style);
}
}

View File

@ -53,7 +53,7 @@ class ConfigInstallTest extends KernelTestBase {
// Ensure that schema provided by modules that are not installed is not
// available.
$this->assertFalse(\Drupal::service('config.typed')->hasConfigSchema('config_schema_test.schema_in_install'), 'Configuration schema for config_schema_test.schema_in_install does not exist.');
$this->assertFalse(\Drupal::service('config.typed')->hasConfigSchema('config_schema_test.someschema'), 'Configuration schema for config_schema_test.someschema does not exist.');
// Install the test module.
$this->installModules(array('config_test'));
@ -80,16 +80,12 @@ class ConfigInstallTest extends KernelTestBase {
$this->installConfig(array('config_schema_test'));
// After module installation the new schema should exist.
$this->assertTrue(\Drupal::service('config.typed')->hasConfigSchema('config_schema_test.schema_in_install'), 'Configuration schema for config_schema_test.schema_in_install exists.');
// Ensure that data type casting is applied during config installation.
$config = $this->config('config_schema_test.schema_in_install');
$this->assertIdentical($config->get('integer'), 1);
$this->assertTrue(\Drupal::service('config.typed')->hasConfigSchema('config_schema_test.someschema'), 'Configuration schema for config_schema_test.someschema exists.');
// Test that uninstalling configuration removes configuration schema.
$this->config('core.extension')->set('module', array())->save();
\Drupal::service('config.manager')->uninstall('module', 'config_test');
$this->assertFalse(\Drupal::service('config.typed')->hasConfigSchema('config_schema_test.schema_in_install'), 'Configuration schema for config_schema_test.schema_in_install does not exist.');
$this->assertFalse(\Drupal::service('config.typed')->hasConfigSchema('config_schema_test.someschema'), 'Configuration schema for config_schema_test.someschema does not exist.');
}
/**

View File

@ -209,6 +209,7 @@ class ConfigSchemaTest extends KernelTestBase {
$this->assertEqual($definition, $expected, 'Retrieved the right metadata for the first effect of image.style.medium');
$a = \Drupal::config('config_test.dynamic.third_party');
$test = \Drupal::service('config.typed')->get('config_test.dynamic.third_party')->get('third_party_settings.config_schema_test');
$definition = $test->getDataDefinition()->toArray();
$expected = array();

View File

@ -125,14 +125,6 @@ config_schema_test.schema_data_types:
sequence:
- type: boolean
config_schema_test.schema_in_install:
label: 'Schema test data with parenting'
type: config_object
mapping:
integer:
type: integer
label: 'Integer'
config_schema_test_integer:
type: integer
label: 'Config test integer'

View File

@ -2,7 +2,7 @@ array: []
boolean: true
exp: 1.2e+34
float: 3.14159
float_as_integer: 1
float_as_integer: !!float 1
hex: 0xC
int: 99
octal: 0775

View File

@ -3,4 +3,4 @@ id: override
label: Default
weight: 0
protected_property: Default
status: 1
status: true

View File

@ -4,7 +4,7 @@ id: override_unmet
label: Default
weight: 0
protected_property: Default
status: 1
status: true
dependencies:
module:
- tour

View File

@ -36,6 +36,13 @@ use Drupal\contact\ContactFormInterface;
* "delete-form" = "/admin/structure/contact/manage/{contact_form}/delete",
* "edit-form" = "/admin/structure/contact/manage/{contact_form}",
* "collection" = "/admin/structure/contact",
* },
* config_export = {
* "id",
* "label",
* "recipients",
* "reply",
* "weight",
* }
* )
*/

View File

@ -18,6 +18,12 @@ use Drupal\editor\EditorInterface;
* label = @Translation("Text Editor"),
* entity_keys = {
* "id" = "format"
* },
* config_export = {
* "format",
* "editor",
* "settings",
* "image_upload",
* }
* )
*/

View File

@ -28,6 +28,20 @@ use Drupal\field\FieldConfigInterface;
* entity_keys = {
* "id" = "id",
* "label" = "label"
* },
* config_export = {
* "id",
* "field_name",
* "entity_type",
* "bundle",
* "label",
* "description",
* "required",
* "translatable",
* "default_value",
* "default_value_callback",
* "settings",
* "field_type",
* }
* )
*/

View File

@ -30,6 +30,19 @@ use Drupal\field\FieldStorageConfigInterface;
* entity_keys = {
* "id" = "id",
* "label" = "id"
* },
* config_export = {
* "id",
* "field_name",
* "entity_type",
* "type",
* "settings",
* "module",
* "locked",
* "cardinality",
* "translatable",
* "indexes",
* "persist_with_no_fields",
* }
* )
*/

View File

@ -22,7 +22,7 @@ class FieldConfigEntityUnitTest extends UnitTestCase {
/**
* The entity type used for testing.
*
* @var \Drupal\Core\Entity\EntityTypeInterface|\PHPUnit_Framework_MockObject_MockObject
* @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $entityType;

View File

@ -18,13 +18,6 @@ use Drupal\Tests\UnitTestCase;
*/
class FieldStorageConfigEntityUnitTest extends UnitTestCase {
/**
* The entity type used for testing.
*
* @var \Drupal\Core\Entity\EntityTypeInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $entityType;
/**
* The entity manager used for testing.
*
@ -64,7 +57,7 @@ class FieldStorageConfigEntityUnitTest extends UnitTestCase {
*/
public function testCalculateDependencies() {
// Create a mock entity type for FieldStorageConfig.
$fieldStorageConfigentityType = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface');
$fieldStorageConfigentityType = $this->getMock('\Drupal\Core\Config\Entity\ConfigEntityTypeInterface');
$fieldStorageConfigentityType->expects($this->any())
->method('getProvider')
->will($this->returnValue('field'));

View File

@ -116,10 +116,10 @@ class EntityDisplayTest extends KernelTestBase {
$display->setComponent('component_3');
$display->setComponent('component_1');
$display->setComponent('component_2');
$display->removeComponent('name');
$display->save();
$components = array_keys($display->getComponents());
$expected = array ( 0 => 'component_1', 1 => 'component_2', 2 => 'component_3',);
// The name field is not configurable so will be added automatically.
$expected = array ( 0 => 'component_1', 1 => 'component_2', 2 => 'component_3', 'name');
$this->assertIdentical($components, $expected);
}

View File

@ -236,7 +236,7 @@ display:
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: 0
field_api_classes: false
plugin_id: field
entity_type: file
entity_field: filename

View File

@ -41,6 +41,13 @@ use Drupal\filter\Plugin\FilterInterface;
* links = {
* "edit-form" = "/admin/config/content/formats/manage/{filter_format}",
* "disable" = "/admin/config/content/formats/manage/{filter_format}/disable"
* },
* config_export = {
* "name",
* "format",
* "weight",
* "roles",
* "filters",
* }
* )
*/

View File

@ -1,6 +1,8 @@
langcode: en
status: true
dependencies:
module:
- forum
enforced:
module:
- forum

View File

@ -1,6 +1,8 @@
langcode: en
status: true
dependencies:
module:
- forum
enforced:
module:
- forum

View File

@ -48,6 +48,11 @@ use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
* "edit-form" = "/admin/config/media/image-styles/manage/{image_style}",
* "delete-form" = "/admin/config/media/image-styles/manage/{image_style}/delete",
* "collection" = "/admin/config/media/image-styles",
* },
* config_export = {
* "name",
* "label",
* "effects",
* }
* )
*/

View File

@ -50,13 +50,16 @@ class LanguageConfigOverride extends StorableConfigBase {
/**
* {@inheritdoc}
*/
public function save() {
// @todo Use configuration schema to validate.
// https://drupal.org/node/2270399
// Perform basic data validation.
foreach ($this->data as $key => $value) {
$this->validateValue($key, $value);
public function save($has_trusted_data = FALSE) {
if (!$has_trusted_data) {
// @todo Use configuration schema to validate.
// https://drupal.org/node/2270399
// Perform basic data validation.
foreach ($this->data as $key => $value) {
$this->validateValue($key, $value);
}
}
$this->storage->write($this->name, $this->data);
// Invalidate the cache tags not only when updating, but also when creating,
// because a language config override object uses the same cache tag as the

View File

@ -4,3 +4,6 @@ status: true
langcode: en
type: node
plugin: node_promote_action
dependencies:
module:
- node

View File

@ -4,3 +4,6 @@ status: true
langcode: en
type: node
plugin: node_publish_action
dependencies:
module:
- node

View File

@ -37,6 +37,15 @@ use Drupal\node\NodeTypeInterface;
* "edit-form" = "/admin/structure/types/manage/{node_type}",
* "delete-form" = "/admin/structure/types/manage/{node_type}/delete",
* "collection" = "/admin/structure/types",
* },
* config_export = {
* "name",
* "type",
* "description",
* "help",
* "new_revision",
* "preview_mode",
* "display_submitted",
* }
* )
*/

View File

@ -110,4 +110,11 @@ class ListFloatItem extends ListItemBase {
return $values;
}
/**
* {@inheritdoc}
*/
protected static function castAllowedValue($value) {
return (float) $value;
}
}

View File

@ -73,4 +73,11 @@ class ListIntegerItem extends ListItemBase {
}
}
/**
* {@inheritdoc}
*/
protected static function castAllowedValue($value) {
return (int) $value;
}
}

View File

@ -314,11 +314,24 @@ abstract class ListItemBase extends FieldItemBase implements OptionsProviderInte
$label = static::structureAllowedValues($label);
}
$structured_values[] = array(
'value' => $value,
'value' => static::castAllowedValue($value),
'label' => $label,
);
}
return $structured_values;
}
/**
* Converts a value to the correct type.
*
* @param mixed $value
* The value to cast.
*
* @return mixed
* The casted value.
*/
protected static function castAllowedValue($value) {
return $value;
}
}

View File

@ -75,4 +75,11 @@ class ListStringItem extends ListItemBase {
}
}
/**
* {@inheritdoc}
*/
protected static function castAllowedValue($value) {
return (string) $value;
}
}

View File

@ -11,7 +11,7 @@ type: list_float
settings:
allowed_values:
-
value: 0
value: !!float 0
label: Zero
-
value: 0.5

View File

@ -20,6 +20,13 @@ use Drupal\rdf\RdfMappingInterface;
* config_prefix = "mapping",
* entity_keys = {
* "id" = "id"
* },
* config_export = {
* "id",
* "targetEntityType",
* "bundle",
* "types",
* "fieldMappings",
* }
* )
*/

View File

@ -47,6 +47,14 @@ use Drupal\search\SearchPageInterface;
* "label" = "label",
* "weight" = "weight",
* "status" = "status"
* },
* config_export = {
* "id",
* "label",
* "path",
* "weight",
* "plugin",
* "configuration",
* }
* )
*/

View File

@ -53,7 +53,7 @@ function shortcut_install() {
// Theme settings are not configuration entities and cannot depend on modules
// so to set a module-specific setting, we need to set it with logic.
if (\Drupal::service('theme_handler')->themeExists('seven')) {
\Drupal::configFactory()->getEditable('seven.settings')->set('third_party_settings.shortcut.module_link', TRUE)->save();
\Drupal::configFactory()->getEditable('seven.settings')->set('third_party_settings.shortcut.module_link', TRUE)->save(TRUE);
}
}
@ -64,6 +64,6 @@ function shortcut_uninstall() {
// Theme settings are not configuration entities and cannot depend on modules
// so to unset a module-specific setting, we need to unset it with logic.
if (\Drupal::service('theme_handler')->themeExists('seven')) {
\Drupal::configFactory()->getEditable('seven.settings')->clear('third_party_settings.shortcut.module_link')->save();
\Drupal::configFactory()->getEditable('seven.settings')->clear('third_party_settings.shortcut.module_link')->save(TRUE);
}
}

View File

@ -415,7 +415,7 @@ function shortcut_themes_installed($theme_list) {
// Theme settings are not configuration entities and cannot depend on modules
// so to set a module-specific setting, we need to set it with logic.
if (\Drupal::moduleHandler()->moduleExists('shortcut')) {
\Drupal::configFactory()->getEditable('seven.settings')->set('third_party_settings.shortcut.module_link', TRUE)->save();
\Drupal::configFactory()->getEditable('seven.settings')->set('third_party_settings.shortcut.module_link', TRUE)->save(TRUE);
}
}
}

View File

@ -40,6 +40,10 @@ use Drupal\shortcut\ShortcutSetInterface;
* "delete-form" = "/admin/config/user-interface/shortcut/manage/{shortcut_set}/delete",
* "edit-form" = "/admin/config/user-interface/shortcut/manage/{shortcut_set}",
* "collection" = "/admin/config/user-interface/shortcut",
* },
* config_export = {
* "id",
* "label",
* }
* )
*/

View File

@ -24,6 +24,13 @@ use Drupal\Component\Plugin\ConfigurablePluginInterface;
* entity_keys = {
* "id" = "id",
* "label" = "label"
* },
* config_export = {
* "id",
* "label",
* "type",
* "plugin",
* "configuration",
* }
* )
*/

View File

@ -24,6 +24,12 @@ use Drupal\system\MenuInterface;
* entity_keys = {
* "id" = "id",
* "label" = "label"
* },
* config_export = {
* "id",
* "label",
* "description",
* "locked",
* }
* )
*/

View File

@ -665,7 +665,7 @@ function system_install() {
if (!$site->get('name')) {
$site->set('name', 'Drupal');
}
$site->save();
$site->save(TRUE);
}
/**

View File

@ -8,6 +8,8 @@ langcode: en
locked: false
pattern: 'U'
dependencies:
theme:
- test_basetheme
enforced:
theme:
- test_basetheme

View File

@ -41,6 +41,13 @@ use Drupal\taxonomy\VocabularyInterface;
* "overview-form" = "/admin/structure/taxonomy/manage/{taxonomy_vocabulary}/overview",
* "edit-form" = "/admin/structure/taxonomy/manage/{taxonomy_vocabulary}",
* "collection" = "/admin/structure/taxonomy",
* },
* config_export = {
* "name",
* "vid",
* "description",
* "hierarchy",
* "weight",
* }
* )
*/

View File

@ -24,6 +24,13 @@ use Drupal\tour\TourInterface;
* entity_keys = {
* "id" = "id",
* "label" = "label"
* },
* config_export = {
* "id",
* "label",
* "module",
* "routes",
* "tips",
* }
* )
*/

View File

@ -567,7 +567,7 @@ display:
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: 0
field_api_classes: false
plugin_id: field
entity_type: user
entity_field: mail

View File

@ -40,6 +40,13 @@ use Drupal\user\RoleInterface;
* "edit-form" = "/admin/people/roles/manage/{user_role}",
* "edit-permissions-form" = "/admin/people/permissions/{user_role}",
* "collection" = "/admin/people/roles",
* },
* config_export = {
* "id",
* "label",
* "weight",
* "is_admin",
* "permissions",
* }
* )
*/

View File

@ -988,7 +988,7 @@ function user_user_role_insert(RoleInterface $role) {
),
'plugin' => 'user_add_role_action',
));
$action->save();
$action->trustData()->save();
}
$remove_id = 'user_remove_role_action.' . $role->id();
if (!entity_load('action', $remove_id)) {
@ -1001,7 +1001,7 @@ function user_user_role_insert(RoleInterface $role) {
),
'plugin' => 'user_remove_role_action',
));
$action->save();
$action->trustData()->save();
}
}
@ -1149,7 +1149,7 @@ function user_role_grant_permissions($rid, array $permissions = array()) {
foreach ($permissions as $permission) {
$role->grantPermission($permission);
}
$role->save();
$role->trustData()->save();
}
}
@ -1170,7 +1170,7 @@ function user_role_revoke_permissions($rid, array $permissions = array()) {
foreach ($permissions as $permission) {
$role->revokePermission($permission);
}
$role->save();
$role->trustData()->save();
}
/**

View File

@ -29,6 +29,17 @@ use Drupal\views\ViewEntityInterface;
* "id" = "id",
* "label" = "label",
* "status" = "status"
* },
* config_export = {
* "id",
* "label",
* "module",
* "description",
* "tag",
* "base_table",
* "base_field",
* "core",
* "display",
* }
* )
*/

View File

@ -2293,7 +2293,7 @@ abstract class DisplayPluginBase extends PluginBase implements DisplayPluginInte
$cache_plugin->alterCacheMetadata($is_cacheable, $cache_contexts);
}
return [$is_cacheable, $cache_contexts];
return [(bool) $is_cacheable, $cache_contexts];
}
/**

View File

@ -1285,4 +1285,18 @@ class ViewUI implements ViewEntityInterface {
return $this->storage->getThirdPartyProviders();
}
/**
* {@inheritdoc}
*/
public function trustData() {
return $this->storage->trustData();
}
/**
* {@inheritdoc}
*/
public function hasTrustedData() {
return $this->storage->hasTrustedData();
}
}

View File

@ -13,8 +13,8 @@
*/
function minimal_install() {
// Disable the user pictures on nodes.
\Drupal::configFactory()->getEditable('system.theme.global')->set('features.node_user_picture', FALSE)->save();
\Drupal::configFactory()->getEditable('system.theme.global')->set('features.node_user_picture', FALSE)->save(TRUE);
// Allow visitor account creation, but with administrative approval.
\Drupal::configFactory()->getEditable('user.settings')->set('register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL)->save();
\Drupal::configFactory()->getEditable('user.settings')->set('register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL)->save(TRUE);
}

View File

@ -23,11 +23,11 @@ function standard_install() {
\Drupal::service('entity.definition_update_manager')->applyUpdates();
// Set front page to "node".
\Drupal::configFactory()->getEditable('system.site')->set('page.front', 'node')->save();
\Drupal::configFactory()->getEditable('system.site')->set('page.front', 'node')->save(TRUE);
// Allow visitor account creation with administrative approval.
$user_settings = \Drupal::configFactory()->getEditable('user.settings');
$user_settings->set('register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL)->save();
$user_settings->set('register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL)->save(TRUE);
// Enable default permissions for system roles.
user_role_grant_permissions(RoleInterface::ANONYMOUS_ID, array('access comments'));
@ -41,7 +41,7 @@ function standard_install() {
// Enable the Contact link in the footer menu.
/** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
$menu_link_manager = \Drupal::service('plugin.manager.menu.link');
$menu_link_manager->updateDefinition('contact.site_page', array('enabled' => 1));
$menu_link_manager->updateDefinition('contact.site_page', array('enabled' => TRUE));
user_role_grant_permissions(RoleInterface::ANONYMOUS_ID, array('access site-wide contact form'));
user_role_grant_permissions(RoleInterface::AUTHENTICATED_ID, array('access site-wide contact form'));
@ -67,5 +67,5 @@ function standard_install() {
$shortcut->save();
// Enable the admin theme.
\Drupal::configFactory()->getEditable('node.settings')->set('use_admin_theme', '1')->save();
\Drupal::configFactory()->getEditable('node.settings')->set('use_admin_theme', TRUE)->save(TRUE);
}

View File

@ -23,5 +23,5 @@ function standard_form_install_configure_form_alter(&$form, FormStateInterface $
*/
function standard_form_install_configure_submit($form, FormStateInterface $form_state) {
$site_mail = $form_state->getValue('site_mail');
ContactForm::load('feedback')->setRecipients([$site_mail])->save();
ContactForm::load('feedback')->setRecipients([$site_mail])->trustData()->save();
}

View File

@ -2,7 +2,7 @@ id: override
label: Override
weight: 0
protected_property: Default
status: 1
status: true
dependencies:
module:
- tour

View File

@ -2,7 +2,7 @@ id: override_unmet
label: Override
weight: 0
protected_property: Default
status: 1
status: true
dependencies:
module:
- dblog

View File

@ -28,7 +28,7 @@ class ConfigEntityBaseUnitTest extends UnitTestCase {
/**
* The entity type used for testing.
*
* @var \Drupal\Core\Entity\EntityTypeInterface|\PHPUnit_Framework_MockObject_MockObject
* @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $entityType;
@ -463,9 +463,52 @@ class ConfigEntityBaseUnitTest extends UnitTestCase {
* @covers ::toArray
*/
public function testToArray() {
$this->typedConfigManager->expects($this->never())
->method('getDefinition');
$this->entityType->expects($this->any())
->method('getPropertiesToExport')
->willReturn(['id' => 'configId', 'dependencies' => 'dependencies']);
$properties = $this->entity->toArray();
$this->assertInternalType('array', $properties);
$this->assertEquals(array('configId' => $this->entity->id(), 'dependencies' => array()), $properties);
}
/**
* @covers ::toArray
*/
public function testToArrayIdKey() {
$entity = $this->getMockForAbstractClass('\Drupal\Core\Config\Entity\ConfigEntityBase', [[], $this->entityTypeId], '', TRUE, TRUE, TRUE, ['id', 'get']);
$entity->expects($this->atLeastOnce())
->method('id')
->willReturn($this->id);
$entity->expects($this->once())
->method('get')
->with('dependencies')
->willReturn([]);
$this->typedConfigManager->expects($this->never())
->method('getDefinition');
$this->entityType->expects($this->any())
->method('getPropertiesToExport')
->willReturn(['id' => 'configId', 'dependencies' => 'dependencies']);
$this->entityType->expects($this->once())
->method('getKey')
->with('id')
->willReturn('id');
$properties = $entity->toArray();
$this->assertInternalType('array', $properties);
$this->assertEquals(['configId' => $entity->id(), 'dependencies' => []], $properties);
}
/**
* @covers ::toArray
*/
public function testToArraySchemaFallback() {
$this->typedConfigManager->expects($this->once())
->method('getDefinition')
->will($this->returnValue(array('mapping' => array('id' => '', 'dependencies' => ''))));
$this->entityType->expects($this->any())
->method('getPropertiesToExport')
->willReturn([]);
$properties = $this->entity->toArray();
$this->assertInternalType('array', $properties);
$this->assertEquals(array('id' => $this->entity->id(), 'dependencies' => array()), $properties);
@ -477,6 +520,9 @@ class ConfigEntityBaseUnitTest extends UnitTestCase {
* @expectedException \Drupal\Core\Config\Schema\SchemaIncompleteException
*/
public function testToArrayFallback() {
$this->entityType->expects($this->any())
->method('getPropertiesToExport')
->willReturn([]);
$this->entity->toArray();
}

View File

@ -22,7 +22,7 @@ class ConfigEntityStorageTest extends UnitTestCase {
/**
* The entity type.
*
* @var \Drupal\Core\Entity\EntityTypeInterface|\PHPUnit_Framework_MockObject_MockObject
* @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $entityType;
@ -279,6 +279,9 @@ class ConfigEntityStorageTest extends UnitTestCase {
->method('setData');
$config_object->expects($this->once())
->method('save');
$config_object->expects($this->atLeastOnce())
->method('get')
->willReturn([]);
$this->cacheTagsInvalidator->expects($this->once())
->method('invalidateTags')
@ -343,6 +346,9 @@ class ConfigEntityStorageTest extends UnitTestCase {
->method('setData');
$config_object->expects($this->once())
->method('save');
$config_object->expects($this->atLeastOnce())
->method('get')
->willReturn([]);
$this->cacheTagsInvalidator->expects($this->once())
->method('invalidateTags')
@ -408,6 +414,9 @@ class ConfigEntityStorageTest extends UnitTestCase {
->method('setData');
$config_object->expects($this->once())
->method('save');
$config_object->expects($this->atLeastOnce())
->method('get')
->willReturn([]);
$this->cacheTagsInvalidator->expects($this->once())
->method('invalidateTags')
@ -546,6 +555,9 @@ class ConfigEntityStorageTest extends UnitTestCase {
->will($this->returnValue(TRUE));
$config_object->expects($this->once())
->method('save');
$config_object->expects($this->atLeastOnce())
->method('get')
->willReturn([]);
$this->cacheTagsInvalidator->expects($this->once())
->method('invalidateTags')

View File

@ -131,4 +131,60 @@ class ConfigEntityTypeTest extends UnitTestCase {
);
}
/**
* @covers ::getPropertiesToExport
*
* @dataProvider providerGetPropertiesToExport
*/
public function testGetPropertiesToExport($definition, $expected) {
$entity_type = $this->setUpConfigEntityType($definition);
$properties_to_export = $entity_type->getPropertiesToExport();
$this->assertSame($expected, $properties_to_export);
// Ensure the method is idempotent.
$properties_to_export = $entity_type->getPropertiesToExport();
$this->assertSame($expected, $properties_to_export);
}
public function providerGetPropertiesToExport() {
$data = [];
$data[] = [
[],
NULL,
];
$data[] = [
[
'config_export' => [
'id',
'custom_property' => 'customProperty',
],
],
[
'uuid' => 'uuid',
'langcode' => 'langcode',
'status' => 'status',
'dependencies' => 'dependencies',
'third_party_settings' => 'third_party_settings',
'id' => 'id',
'custom_property' => 'customProperty',
],
];
$data[] = [
[
'config_export' => [
'id',
],
'mergedConfigExport' => [
'random_key' => 'random_key',
],
],
[
'random_key' => 'random_key',
],
];
return $data;
}
}

View File

@ -114,11 +114,15 @@ class StaticMenuLinkOverridesTest extends UnitTestCase {
->with('definitions')
->will($this->returnValue(array()));
$definition_save_1 = array('definitions' => array('test1' => array('parent' => 'test0')));
$definition_save_1 = array(
'definitions' => array(
'test1' => array('parent' => 'test0', 'menu_name' => '', 'weight' => 0, 'expanded' => FALSE, 'enabled' => FALSE)
)
);
$definitions_save_2 = array(
'definitions' => array(
'test1' => array('parent' => 'test0'),
'test1__la___ma' => array('parent' => 'test1')
'test1' => array('parent' => 'test0', 'menu_name' => '', 'weight' => 0, 'expanded' => FALSE, 'enabled' => FALSE),
'test1__la___ma' => array('parent' => 'test1', 'menu_name' => '', 'weight' => 0, 'expanded' => FALSE, 'enabled' => FALSE)
)
);
$config->expects($this->at(2))