diff --git a/core/lib/Drupal/Core/Config/Config.php b/core/lib/Drupal/Core/Config/Config.php index c0d2617..cd8a619 100644 --- a/core/lib/Drupal/Core/Config/Config.php +++ b/core/lib/Drupal/Core/Config/Config.php @@ -10,6 +10,8 @@ use Drupal\Component\Utility\NestedArray; use Drupal\Core\Config\ConfigNameException; use Drupal\Core\Config\Context\ContextInterface; +use Drupal\Core\TypedData\TypedDataInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; /** * Defines the default configuration object. @@ -320,7 +322,12 @@ public function set($key, $value) { $this->load(); } // Type-cast value into a string. - $value = $this->castValue($value); + if ($value instanceof TypedDataInterface) { + $value = $value->getString(); + } + else { + $value = $this->castValue($value); + } // The dot/period is a reserved character; it may appear between keys, but // not within keys. diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityNGBase.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityNGBase.php new file mode 100644 index 0000000..8595f0c --- /dev/null +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityNGBase.php @@ -0,0 +1,129 @@ +originalID; + } + + /** + * Implements ConfigEntityInterface::setOriginalID(). + */ + public function setOriginalID($id) { + $this->originalID = $id; + } + + /** + * Implements \Drupal\Core\Config\Entity\ConfigEntityInterface::enable(). + */ + public function enable() { + $this->status = TRUE; + return $this; + } + + /** + * Implements \Drupal\Core\Config\Entity\ConfigEntityInterface::disable(). + */ + public function disable() { + $this->status = FALSE; + return $this; + } + + /** + * Implements \Drupal\Core\Config\Entity\ConfigEntityInterface::status(). + */ + public function status() { + return !empty($this->status->value); + } + + /** + * Overrides Entity::isNew(). + * + * EntityInterface::enforceIsNew() is only supported for newly created + * configuration entities but has no effect after saving, since each + * configuration entity is unique. + */ + final public function isNew() { + // Configuration entity IDs are strings, and '0' is a valid ID. + return !empty($this->enforceIsNew) || $this->id() === NULL || $this->id() === ''; + } + + /** + * Overrides Entity::createDuplicate(). + */ + public function createDuplicate() { + $duplicate = parent::createDuplicate(); + // Prevent the new duplicate from being misinterpreted as a rename. + $duplicate->setOriginalID(NULL); + return $duplicate; + } + + /** + * Helper callback for uasort() to sort configuration entities by weight and label. + */ + public static function sort($a, $b) { + $a_weight = isset($a->weight) ? $a->weight : 0; + $b_weight = isset($b->weight) ? $b->weight : 0; + if ($a_weight == $b_weight) { + $a_label = $a->label(); + $b_label = $b->label(); + return strnatcasecmp($a_label, $b_label); + } + return ($a_weight < $b_weight) ? -1 : 1; + } + + /** + * Overrides \Drupal\Core\Entity\Entity::getExportProperties(). + */ + public function getExportProperties() { + // Configuration objects do not have a schema. Extract all key names from + // class properties. + $class_info = new \ReflectionClass($this); + $properties = array(); + foreach ($class_info->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { + $name = $property->getName(); + if ($this->getPropertyDefinition($name)) { + $properties[$name] = $this->get($name); + } + else { + $properties[$name] = $this->{$name}; + } + } + return $properties; + } + +} diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php index 92e4be3..8ca433e 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php @@ -134,7 +134,7 @@ public function load(array $ids = NULL) { // Remove any invalid ids from the array. $passed_ids = array_intersect_key($passed_ids, $entities); foreach ($entities as $entity) { - $passed_ids[$entity->{$this->idKey}] = $entity; + $passed_ids[$entity->id()] = $entity; } $entities = $passed_ids; } diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigStorageControllerNG.php b/core/lib/Drupal/Core/Config/Entity/ConfigStorageControllerNG.php new file mode 100644 index 0000000..0f50e37 --- /dev/null +++ b/core/lib/Drupal/Core/Config/Entity/ConfigStorageControllerNG.php @@ -0,0 +1,186 @@ +getConfigPrefix(); + + // Load all of the configuration entities. + if ($ids === NULL) { + $names = drupal_container()->get('config.storage')->listAll($prefix); + $result = array(); + foreach ($names as $name) { + $config = config($name); + $result[$config->get($this->idKey)] = $config->get(); + } + return $result; + } + else { + $result = array(); + foreach ($ids as $id) { + // Add the prefix to the ID to serve as the configuration object name. + $config = config($prefix . $id); + if (!$config->isNew()) { + $result[$id] = $config->get(); + } + } + return $result; + } + } + + /** + * Overrides ConfigStorageController::attachLoad(). + */ + protected function attachLoad(&$queried_entities, $revision_id = FALSE) { + // Map the loaded records into entity objects and according fields. + $class = $this->entityInfo['class']; + $entities = array(); + foreach ($queried_entities as $id => $record) { + $values = array(); + 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. + $values[$name][LANGUAGE_DEFAULT] = $value; + } + // Turn the record into an entity class. + $entities[$id] = new $class($values, $this->entityType); + $original_id = $entities[$id]->id(); + if ($original_id !== NULL && $original_id !== '') { + $entities[$id]->setOriginalID($original_id); + } + } + $queried_entities = $entities; + + parent::attachLoad($queried_entities, $revision_id); + } + + /** + * Overrides ConfigStorageController::create(). + */ + public function create(array $values) { + $class = $this->entityInfo['class']; + + $entity = new $class($values, $this->entityType); + // Mark this entity as new, so isNew() returns TRUE. This does not check + // whether a configuration entity with the same ID (if any) already exists. + $entity->enforceIsNew(); + + + // Assign a new UUID if there is none yet. + if (!isset($entity->{$this->uuidKey}->value)) { + $uuid = new Uuid(); + $entity->{$this->uuidKey} = $uuid->generate(); + } + if (!isset($entity->status->value)) { + $entity->status = TRUE; + } + + // Set all other given values. + foreach ($values as $name => $value) { + $entity->$name = $value; + } + + $original_id = $entity->id(); + if ($original_id !== NULL && $original_id !== '') { + $entity->setOriginalID($original_id); + } + + // Modules might need to add or change the data initially held by the new + // entity object, for instance to fill-in default values. + $this->invokeHook('create', $entity); + + // Default status to enabled. + if (!empty($this->statusKey) && !isset($entity->{$this->statusKey})) { + $entity->{$this->statusKey} = TRUE; + } + + return $entity; + } + + /** + * Overrides ConfigStorageController::postSave(). + */ + protected function postSave(EntityInterface $entity, $update) { + // Delete the original configuration entity, in case the entity ID was + // renamed. + if ($update && !empty($entity->original) && $entity->{$this->idKey}->value !== $entity->original->{$this->idKey}->value) { + // @todo This should just delete the original config object without going + // through the API, no? + $entity->original->delete(); + } + } + + /** + * Implements Drupal\Core\Entity\EntityStorageControllerInterface::getFieldDefinitions(). + */ + public function getFieldDefinitions(array $constraints) { + // @todo: Add caching for $this->entityFieldInfo. + if (!isset($this->entityFieldInfo)) { + $this->entityFieldInfo = array( + 'definitions' => $this->baseFieldDefinitions(), + // Contains definitions of optional (per-bundle) fields. + 'optional' => array(), + // An array keyed by bundle name containing the optional fields added by + // the bundle. + 'bundle map' => array(), + ); + + // Invoke hooks. + $result = module_invoke_all($this->entityType . '_property_info'); + $this->entityFieldInfo = NestedArray::mergeDeep($this->entityFieldInfo, $result); + $result = module_invoke_all('entity_field_info', $this->entityType); + $this->entityFieldInfo = NestedArray::mergeDeep($this->entityFieldInfo, $result); + + $hooks = array('entity_field_info', $this->entityType . '_property_info'); + drupal_alter($hooks, $this->entityFieldInfo, $this->entityType); + + // Enforce fields to be multiple by default. + foreach ($this->entityFieldInfo['definitions'] as &$definition) { + $definition['list'] = TRUE; + } + foreach ($this->entityFieldInfo['optional'] as &$definition) { + $definition['list'] = TRUE; + } + } + + $bundle = !empty($constraints['Bundle']) ? $constraints['Bundle'] : FALSE; + + // Add in per-bundle fields. + if (!isset($this->fieldDefinitions[$bundle])) { + $this->fieldDefinitions[$bundle] = $this->entityFieldInfo['definitions']; + + if ($bundle && isset($this->entityFieldInfo['bundle map'][$bundle])) { + $this->fieldDefinitions[$bundle] += array_intersect_key($this->entityFieldInfo['optional'], array_flip($this->entityFieldInfo['bundle map'][$bundle])); + } + } + return $this->fieldDefinitions[$bundle]; + } + + /** + * Implements \Drupal\Core\Entity\DataBaseStorageControllerNG::baseFieldDefinitions(). + */ + public function baseFieldDefinitions() { + return array(); + } + +} diff --git a/core/lib/Drupal/Core/Entity/EntityNG.php b/core/lib/Drupal/Core/Entity/EntityNG.php index 70ac362..e954254 100644 --- a/core/lib/Drupal/Core/Entity/EntityNG.php +++ b/core/lib/Drupal/Core/Entity/EntityNG.php @@ -336,7 +336,7 @@ public function getTranslationLanguages($include_default = TRUE) { if (!$field->isEmpty()) { $translations[$langcode] = TRUE; } - if (isset($this->values[$name])) { + if (isset($this->values[$name]) && is_array($this->values[$name])) { foreach ($this->values[$name] as $langcode => $values) { // If a value is there but the field object is empty, it has been // unset, so we need to skip the field also. diff --git a/core/lib/Drupal/Core/Entity/Field/FieldItemBase.php b/core/lib/Drupal/Core/Entity/Field/FieldItemBase.php index 62edc8d..9abdbb0 100644 --- a/core/lib/Drupal/Core/Entity/Field/FieldItemBase.php +++ b/core/lib/Drupal/Core/Entity/Field/FieldItemBase.php @@ -116,7 +116,7 @@ public function getString() { foreach ($this->getProperties() as $property) { $strings[] = $property->getString(); } - return implode(', ', array_filter($strings)); + return implode(', ', $strings); } /** diff --git a/core/lib/Drupal/Core/TypedData/TypedData.php b/core/lib/Drupal/Core/TypedData/TypedData.php index d6ad4be..33d169e 100644 --- a/core/lib/Drupal/Core/TypedData/TypedData.php +++ b/core/lib/Drupal/Core/TypedData/TypedData.php @@ -34,6 +34,10 @@ public function __construct(array $definition) { $this->definition = $definition; } + public function __toString() { + return $this->getString(); + } + /** * Implements \Drupal\Core\TypedData\TypedDataInterface::getType(). */ diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigEntityListTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityListTest.php index f851cbc..250a3ca 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigEntityListTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityListTest.php @@ -76,7 +76,7 @@ function testList() { $actual_operations = $controller->getOperations($entity); // Sort the operations to normalize link order. uasort($actual_operations, 'drupal_sort_weight'); - $this->assertIdentical($expected_operations, $actual_operations); + $this->assertIdentical($expected_operations, $actual_operations, 'The operations are built correctly.'); // Test buildHeader() method. $expected_items = array( diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigEntityStatusTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityStatusTest.php index 5519de5..a9db180 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigEntityStatusTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityStatusTest.php @@ -19,7 +19,7 @@ class ConfigEntityStatusTest extends DrupalUnitTestBase { * * @var array */ - public static $modules = array('config_test'); + public static $modules = array('config_test', 'system'); public static function getInfo() { return array( diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigEntityStatusUITest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityStatusUITest.php index 642fe2e..9f729ea 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigEntityStatusUITest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityStatusUITest.php @@ -20,7 +20,7 @@ class ConfigEntityStatusUITest extends WebTestBase { * * @var array */ - public static $modules = array('config_test'); + public static $modules = array('config_test', 'system'); public static function getInfo() { return array( diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigEntityTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityTest.php index e93b79f..bdfbbc1 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigEntityTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityTest.php @@ -36,11 +36,11 @@ public static function getInfo() { function testCRUD() { // Verify default properties on a newly created empty entity. $empty = entity_create('config_test', array()); - $this->assertIdentical($empty->id, NULL); - $this->assertTrue($empty->uuid); - $this->assertIdentical($empty->label, NULL); - $this->assertIdentical($empty->style, NULL); - $this->assertIdentical($empty->langcode, LANGUAGE_NOT_SPECIFIED); + $this->assertIdentical($empty->id->value, NULL); + $this->assertTrue($empty->uuid->value); + $this->assertIdentical($empty->label->value, NULL); + $this->assertIdentical($empty->style->value, NULL); + $this->assertIdentical($empty->langcode->value, LANGUAGE_NOT_SPECIFIED); // Verify ConfigEntity properties/methods on the newly created empty entity. $this->assertIdentical($empty->isNew(), TRUE); @@ -50,11 +50,11 @@ function testCRUD() { $this->assertTrue($empty->uuid()); $this->assertIdentical($empty->label(), NULL); - $this->assertIdentical($empty->get('id'), NULL); - $this->assertTrue($empty->get('uuid')); - $this->assertIdentical($empty->get('label'), NULL); - $this->assertIdentical($empty->get('style'), NULL); - $this->assertIdentical($empty->get('langcode'), LANGUAGE_NOT_SPECIFIED); + $this->assertIdentical($empty->get('id')->value, NULL); + $this->assertTrue($empty->get('uuid')->value); + $this->assertIdentical($empty->get('label')->value, NULL); + $this->assertIdentical($empty->get('style')->value, NULL); + $this->assertIdentical($empty->get('langcode')->value, LANGUAGE_NOT_SPECIFIED); // Verify Entity properties/methods on the newly created empty entity. $this->assertIdentical($empty->isNewRevision(), FALSE); @@ -91,12 +91,12 @@ function testCRUD() { 'label' => $this->randomString(), 'style' => $this->randomName(), )); - $this->assertIdentical($config_test->id, $expected['id']); - $this->assertTrue($config_test->uuid); - $this->assertNotEqual($config_test->uuid, $empty->uuid); - $this->assertIdentical($config_test->label, $expected['label']); - $this->assertIdentical($config_test->style, $expected['style']); - $this->assertIdentical($config_test->langcode, LANGUAGE_NOT_SPECIFIED); + $this->assertIdentical($config_test->id->value, $expected['id']); + $this->assertTrue($config_test->uuid->value); + $this->assertNotEqual($config_test->uuid->value, $empty->uuid->value); + $this->assertIdentical($config_test->label->value, $expected['label']); + $this->assertIdentical($config_test->style->value, $expected['style']); + $this->assertIdentical($config_test->langcode->value, LANGUAGE_NOT_SPECIFIED); // Verify methods on the newly created entity. $this->assertIdentical($config_test->isNew(), TRUE); @@ -179,7 +179,7 @@ function testCRUD() { // Test config entity prepopulation. state()->set('config_test.prepopulate', TRUE); $config_test = entity_create('config_test', array('foo' => 'bar')); - $this->assertEqual($config_test->get('foo'), 'baz', 'Initial value correctly populated'); + $this->assertEqual($config_test->foo, 'baz', 'Initial value correctly populated'); } /** diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php index 9278269..ece4c59 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php @@ -33,7 +33,6 @@ function setUp() { parent::setUp(); $this->installSchema('system', 'config_snapshot'); - config_install_default_config('module', 'config_test'); // Installing config_test's default configuration pollutes the global // variable being used for recording hook invocations by this test already, diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigInstallTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigInstallTest.php index 4cb9ea3..cedd579 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigInstallTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigInstallTest.php @@ -13,6 +13,14 @@ * Tests installation of configuration objects in installation functionality. */ class ConfigInstallTest extends DrupalUnitTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = array('system'); + public static function getInfo() { return array( 'name' => 'Installation functionality unit tests', diff --git a/core/modules/config/tests/config_test/config_test.module b/core/modules/config/tests/config_test/config_test.module index d0d59fd..bbd505d 100644 --- a/core/modules/config/tests/config_test/config_test.module +++ b/core/modules/config/tests/config_test/config_test.module @@ -155,7 +155,7 @@ function config_test_cache_flush() { */ function config_test_config_test_create(ConfigTest $config_test) { if (state()->get('config_test.prepopulate')) { - $config_test->set('foo', 'baz'); + $config_test->foo = 'baz'; } } diff --git a/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTestFormController.php b/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTestFormController.php index 7e54e1c..51d1943 100644 --- a/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTestFormController.php +++ b/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTestFormController.php @@ -8,12 +8,12 @@ namespace Drupal\config_test; use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\Entity\EntityFormController; +use Drupal\Core\Entity\EntityFormControllerNG; /** * Form controller for the test config edit forms. */ -class ConfigTestFormController extends EntityFormController { +class ConfigTestFormController extends EntityFormControllerNG { /** * Overrides Drupal\Core\Entity\EntityFormController::form(). diff --git a/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTestStorageController.php b/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTestStorageController.php index e1fe4fe..8b21022 100644 --- a/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTestStorageController.php +++ b/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTestStorageController.php @@ -7,13 +7,13 @@ namespace Drupal\config_test; -use Drupal\Core\Config\Entity\ConfigStorageController; +use Drupal\Core\Config\Entity\ConfigStorageControllerNG; use Drupal\Core\Config\Config; /** * @todo. */ -class ConfigTestStorageController extends ConfigStorageController { +class ConfigTestStorageController extends ConfigStorageControllerNG { /** * Overrides \Drupal\Core\Config\Entity\ConfigStorageController::importCreate(). @@ -45,4 +45,48 @@ public function importDelete($name, Config $new_config, Config $old_config) { return parent::importDelete($name, $new_config, $old_config); } + /** + * Implements \Drupal\Core\Entity\DataBaseStorageControllerNG::baseFieldDefinitions(). + */ + public function baseFieldDefinitions() { + $fields = parent::baseFieldDefinitions(); + $fields['id'] = array( + 'label' => t('ID'), + 'description' => t('The ID of the test entity.'), + 'type' => 'string_field', + ); + $fields['uuid'] = array( + 'label' => t('UUID'), + 'description' => t('The UUID of the test entity.'), + 'type' => 'string_field', + ); + $fields['langcode'] = array( + 'label' => t('Language code'), + 'description' => t('The language code of the test entity.'), + 'type' => 'language_field', + ); + $fields['label'] = array( + 'label' => t('Name'), + 'description' => t('The name of the test entity.'), + 'type' => 'string_field', + ); + $fields['style'] = array( + 'label' => t('Style'), + 'description' => t('The ID of the associated image style.'), + 'type' => 'entity_reference_field', + 'settings' => array('target_type' => 'image_style'), + ); + $fields['protected_property'] = array( + 'label' => t('Protected Property'), + 'description' => t('A protected property of the test entity.'), + 'type' => 'string_field', + ); + $fields['status'] = array( + 'label' => t('Status'), + 'description' => t('The status of the test entity.'), + 'type' => 'boolean_field', + ); + return $fields; + } + } diff --git a/core/modules/config/tests/config_test/lib/Drupal/config_test/Plugin/Core/Entity/ConfigTest.php b/core/modules/config/tests/config_test/lib/Drupal/config_test/Plugin/Core/Entity/ConfigTest.php index 28b22cc..e10dd08 100644 --- a/core/modules/config/tests/config_test/lib/Drupal/config_test/Plugin/Core/Entity/ConfigTest.php +++ b/core/modules/config/tests/config_test/lib/Drupal/config_test/Plugin/Core/Entity/ConfigTest.php @@ -7,7 +7,7 @@ namespace Drupal\config_test\Plugin\Core\Entity; -use Drupal\Core\Config\Entity\ConfigEntityBase; +use Drupal\Core\Config\Entity\ConfigEntityNGBase; use Drupal\Component\Annotation\Plugin; use Drupal\Core\Annotation\Translation; @@ -33,7 +33,7 @@ * } * ) */ -class ConfigTest extends ConfigEntityBase { +class ConfigTest extends ConfigEntityNGBase { /** * The machine name for the configuration entity. @@ -70,6 +70,17 @@ class ConfigTest extends ConfigEntityBase { */ protected $protected_property; + protected function init() { + parent::init(); + // We unset all defined properties, so magic getters apply. + unset($this->id); + unset($this->label); + unset($this->uuid); + unset($this->style); + unset($this->protected_property); + unset($this->status); + } + /** * Overrides \Drupal\Core\Config\Entity\ConfigEntityBase::getExportProperties(); */ diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/ConfigEntityQueryTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/ConfigEntityQueryTest.php index bfafc9e..eae8928 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/ConfigEntityQueryTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/ConfigEntityQueryTest.php @@ -21,7 +21,7 @@ class ConfigEntityQueryTest extends DrupalUnitTestBase { * * @var array */ - static $modules = array('config_test'); + static $modules = array('config_test', 'system'); /** * Stores the search results for alter comparision.