diff --git a/core/includes/entity.api.php b/core/includes/entity.api.php index a7fdfcb..fb076b6 100644 --- a/core/includes/entity.api.php +++ b/core/includes/entity.api.php @@ -107,6 +107,8 @@ function hook_entity_view_mode_info_alter(&$view_modes) { * - access callback: As in hook_menu(). 'user_access' will be assumed if * no value is provided. * - access arguments: As in hook_menu(). + * - translatable: (optional) A boolean value specifying whether this bundle + * has translation support enabled. Defaults to FALSE. * * @see entity_get_bundles() * @see hook_entity_bundle_info_alter() 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..ea41a5a --- /dev/null +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityNGBase.php @@ -0,0 +1,137 @@ +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::setStatus(). + */ + public function setStatus($status) { + $this->status = (bool) $status; + 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/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php index fe00ef3..785c81c 100644 --- a/core/lib/Drupal/Core/Entity/Entity.php +++ b/core/lib/Drupal/Core/Entity/Entity.php @@ -440,4 +440,14 @@ public function setContext($name = NULL, ContextAwareInterface $parent = NULL) { // As entities are always the root of the tree of typed data, we do not need // to set any parent or name. } + + /** + * Implements \Drupal\Core\Entity\EntityInterface::isTranslatable(). + */ + public function isTranslatable() { + // @todo Inject the entity manager and retrieve bundle info from it. + $bundles = entity_get_bundles($this->entityType); + return !empty($bundles[$this->bundle()]['translatable']); + } + } diff --git a/core/lib/Drupal/Core/Entity/EntityBCDecorator.php b/core/lib/Drupal/Core/Entity/EntityBCDecorator.php index a23f7e4..08be87c 100644 --- a/core/lib/Drupal/Core/Entity/EntityBCDecorator.php +++ b/core/lib/Drupal/Core/Entity/EntityBCDecorator.php @@ -460,4 +460,12 @@ public function setContext($name = NULL, ContextAwareInterface $parent = NULL) { public function getExportProperties() { $this->decorated->getExportProperties(); } + + /** + * Forwards the call to the decorated entity. + */ + public function isTranslatable() { + return $this->decorated->isTranslatable(); + } + } diff --git a/core/lib/Drupal/Core/Entity/EntityInterface.php b/core/lib/Drupal/Core/Entity/EntityInterface.php index 34038e9..b900490 100644 --- a/core/lib/Drupal/Core/Entity/EntityInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityInterface.php @@ -219,4 +219,13 @@ public function getBCEntity(); * @see \Drupal\Core\Entity\EntityInterface::getBCEntity() */ public function getOriginalEntity(); + + /** + * Returns the translation support status. + * + * @return bool + * TRUE if the entity bundle has translation support enabled. + */ + public function isTranslatable(); + } diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php index a0d7c03..be46ac9 100644 --- a/core/lib/Drupal/Core/Entity/EntityManager.php +++ b/core/lib/Drupal/Core/Entity/EntityManager.php @@ -75,11 +75,8 @@ * - static_cache: (optional) Boolean indicating whether entities should be * statically cached during a page request. Used by * Drupal\Core\Entity\DatabaseStorageController. Defaults to TRUE. - * - translation: (optional) An associative array of modules registered as - * field translation handlers. Array keys are the module names, and array - * values can be any data structure the module uses to provide field - * translation. If the value is empty, the module will not be used as a - * translation handler. + * - translatable: (optional) Boolean indicating whether entities of this type + * have mutlilingual support. Defaults to FALSE. * - entity_keys: An array describing how the Field API can extract certain * information from objects of this entity type. Elements: * - id: The name of the property that contains the primary ID of the 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/TranslatableInterface.php b/core/lib/Drupal/Core/TypedData/TranslatableInterface.php index 744e780..f066b72 100644 --- a/core/lib/Drupal/Core/TypedData/TranslatableInterface.php +++ b/core/lib/Drupal/Core/TypedData/TranslatableInterface.php @@ -52,4 +52,5 @@ public function getTranslationLanguages($include_default = TRUE); * A typed data object for the translated data. */ public function getTranslation($langcode, $strict = TRUE); + } 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/block/custom_block/lib/Drupal/custom_block/Plugin/Core/Entity/CustomBlock.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Core/Entity/CustomBlock.php index c84a761..d0ea6ee 100644 --- a/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Core/Entity/CustomBlock.php +++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Core/Entity/CustomBlock.php @@ -31,6 +31,7 @@ * revision_table = "custom_block_revision", * menu_base_path = "block/%custom_block", * fieldable = TRUE, + * translatable = TRUE, * entity_keys = { * "id" = "id", * "revision" = "revision_id", diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php b/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php index d80a844..f6e9091 100644 --- a/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php +++ b/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php @@ -29,6 +29,7 @@ * base_table = "comment", * uri_callback = "comment_uri", * fieldable = TRUE, + * translatable = TRUE, * static_cache = FALSE, * entity_keys = { * "id" = "cid", 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..721119e 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'; } } @@ -184,3 +184,13 @@ function config_test_entity_disable(ConfigTest $config_test) { $config_test->disable()->save(); return new RedirectResponse(url('admin/structure/config_test', array('absolute' => TRUE))); } + +/** + * Implements hook_entity_info_alter(). + */ +function config_test_entity_info_alter(&$entity_info) { + // The 'translatable' entity key is not supposed to change over time. In this + // case we can safely do it because we set it once and we do not change it for + // all the duration of the test session. + $entity_info['config_test']['translatable'] = Drupal::service('state')->get('config_test.translatable'); +} 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/field/field.multilingual.inc b/core/modules/field/field.multilingual.inc index a9b33ac..8e5ee39 100644 --- a/core/modules/field/field.multilingual.inc +++ b/core/modules/field/field.multilingual.inc @@ -29,17 +29,9 @@ * The available language codes for a particular field are returned by * field_available_languages(). Whether a field is translatable is determined by * calling field_is_translatable(), which checks the $field['translatable'] - * property returned by field_info_field(), and whether there is at least one - * translation handler available for the field. A translation handler is a - * module registering itself via hook_entity_info_alter() to handle field - * translations. + * property returned by field_info_field() and whether the entity type the field + * is attached to supports translation. * - - * By default, _field_invoke() and _field_invoke_multiple() are processing a - * field in all available languages, unless they are given a language code - * suggestion. Based on that suggestion, _field_language_suggestion() determines - * the languages to act on. - * By default, _field_invoke() and _field_invoke_multiple() process a field in * all available languages, unless they are given a language code suggestion. * Based on that suggestion, _field_language_suggestion() determines the @@ -226,23 +218,12 @@ function field_is_translatable($entity_type, $field) { * TRUE, if the given handler is allowed to manage field translations. If no * handler is passed, TRUE means there is at least one registered translation * handler. + * + * @todo Remove this once the migration to the Entity Field API is complete. */ function field_has_translation_handler($entity_type, $handler = NULL) { - $entity_info = entity_get_info($entity_type); - - if (isset($handler)) { - return !empty($entity_info['translation'][$handler]); - } - elseif (isset($entity_info['translation'])) { - foreach ($entity_info['translation'] as $handler_info) { - // The translation handler must use a non-empty data structure. - if (!empty($handler_info)) { - return TRUE; - } - } - } - - return FALSE; + $info = entity_get_info($entity_type); + return !empty($info['translatable']); } /** diff --git a/core/modules/field/tests/modules/field_test/field_test.entity.inc b/core/modules/field/tests/modules/field_test/field_test.entity.inc index 81c116d..565ed08 100644 --- a/core/modules/field/tests/modules/field_test/field_test.entity.inc +++ b/core/modules/field/tests/modules/field_test/field_test.entity.inc @@ -12,13 +12,8 @@ * Implements hook_entity_info_alter(). */ function field_test_entity_info_alter(&$entity_info) { - // Enable/disable field_test as a translation handler. foreach (field_test_entity_info_translatable() as $entity_type => $translatable) { - $entity_info[$entity_type]['translation']['field_test'] = $translatable; - } - // Disable the entity type translation handler. - foreach ($entity_info as $entity_type => $info) { - $entity_info[$entity_type]['translation'][$entity_type] = FALSE; + $entity_info[$entity_type]['translatable'] = $translatable; } } diff --git a/core/modules/language/language.module b/core/modules/language/language.module index 81b9715..1c20695 100644 --- a/core/modules/language/language.module +++ b/core/modules/language/language.module @@ -210,11 +210,7 @@ function language_theme() { function language_entity_supported() { $supported = array(); foreach (entity_get_info() as $entity_type => $info) { - // @todo Revisit this once all core entities are migrated to the Entity - // Field API and language support for configuration entities has been - // sorted out. - $entity_class = new ReflectionClass($info['class']); - if ($info['fieldable'] && !$entity_class->implementsInterface('Drupal\Core\Config\Entity\ConfigEntityInterface')) { + if (!empty($info['fieldable']) && !empty($info['translatable'])) { $supported[$entity_type] = $entity_type; } } diff --git a/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php b/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php index 32c61ca..e367425 100644 --- a/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php +++ b/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php @@ -30,6 +30,7 @@ * revision_table = "node_revision", * uri_callback = "node_uri", * fieldable = TRUE, + * translatable = TRUE, * entity_keys = { * "id" = "nid", * "revision" = "vid", 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. diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Plugin/Core/Entity/EntityTestMul.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Plugin/Core/Entity/EntityTestMul.php index 31844b6..4de550b 100644 --- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Plugin/Core/Entity/EntityTestMul.php +++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Plugin/Core/Entity/EntityTestMul.php @@ -27,6 +27,7 @@ * base_table = "entity_test_mul", * data_table = "entity_test_mul_property_data", * fieldable = TRUE, + * translatable = TRUE, * entity_keys = { * "id" = "id", * "uuid" = "uuid", diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Plugin/Core/Entity/EntityTestMulRev.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Plugin/Core/Entity/EntityTestMulRev.php index a12fc58..632ada7 100644 --- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Plugin/Core/Entity/EntityTestMulRev.php +++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Plugin/Core/Entity/EntityTestMulRev.php @@ -28,6 +28,7 @@ * data_table = "entity_test_mulrev_property_data", * revision_table = "entity_test_mulrev_property_revision", * fieldable = TRUE, + * translatable = TRUE, * entity_keys = { * "id" = "id", * "uuid" = "uuid", diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php index bbb4638..affe712 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php @@ -30,6 +30,7 @@ * base_table = "taxonomy_term_data", * uri_callback = "taxonomy_term_uri", * fieldable = TRUE, + * translatable = TRUE, * entity_keys = { * "id" = "tid", * "bundle" = "vid", diff --git a/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationControllerInterface.php b/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationControllerInterface.php index a42a019..aee3c67 100644 --- a/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationControllerInterface.php +++ b/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationControllerInterface.php @@ -50,9 +50,7 @@ * Additionally some more entity info keys can be defined to further customize * the translation UI. The entity translation info is an associative array that * has to match the following structure. Two nested arrays keyed respectively - * by the 'translation' key and the 'entity_translation' key: the first one is - * the key defined by the core entity system, while the second one registers - * Entity Tanslation as a field translation handler. Elements: + * by the 'translation' key and the 'translation_entity' key. Elements: * - access callback: The access callback for the translation pages. Defaults to * 'entity_translation_translate_access'. * - access arguments: The access arguments for the translation pages. By diff --git a/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/ConfigTestTranslationUITest.php b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/ConfigTestTranslationUITest.php index 2439c8b..9c51c46 100644 --- a/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/ConfigTestTranslationUITest.php +++ b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/ConfigTestTranslationUITest.php @@ -35,6 +35,14 @@ function setUp() { } /** + * Overrides \Drupal\translation_entity\Tests\EntityTranslationUITest::enableTranslation(). + */ + protected function enableTranslation() { + $this->container->get('state')->set('config_test.translatable', TRUE); + parent::enableTranslation(); + } + + /** * Overrides \Drupal\translation_entity\Tests\EntityTranslationUITest::getNewEntityValues(). */ protected function getNewEntityValues($langcode) { diff --git a/core/modules/translation_entity/translation_entity.module b/core/modules/translation_entity/translation_entity.module index bb78d1e..68eb96d 100644 --- a/core/modules/translation_entity/translation_entity.module +++ b/core/modules/translation_entity/translation_entity.module @@ -72,11 +72,12 @@ function translation_entity_language_types_info_alter(array &$language_types) { * Implements hook_entity_info_alter(). */ function translation_entity_entity_info_alter(array &$entity_info) { - $edit_form_info = array(); - - $bundles_info = entity_get_bundles(); // Provide defaults for translation info. foreach ($entity_info as $entity_type => &$info) { + if (empty($info['translatable'])) { + continue; + } + if (!isset($info['translation']['translation_entity'])) { $info['translation']['translation_entity'] = array(); } @@ -87,39 +88,37 @@ function translation_entity_entity_info_alter(array &$entity_info) { // shared accross different entities. $info += array('translation_controller_class' => 'Drupal\translation_entity\EntityTranslationController'); - // Check whether translation is enabled at least for one bundle. We cannot - // use translation_entity_enabled() here since it would cause infinite - // recursion, as it relies on entity info. - $enabled = FALSE; - $bundles = isset($bundles_info[$entity_type]) ? array_keys($bundles_info[$entity_type]) : array($entity_type); - foreach ($bundles as $bundle) { - if (translation_entity_get_config($entity_type, $bundle, 'enabled')) { - $enabled = TRUE; - break; - } + // If no menu base path is provided we default to the usual + // "entity_type/%entity_type" pattern. + if (!isset($info['menu_base_path'])) { + $path = "$entity_type/%$entity_type"; + $info['menu_base_path'] = $path; } - if ($enabled) { - // If no menu base path is provided we default to the usual - // "entity_type/%entity_type" pattern. - if (!isset($info['menu_base_path'])) { - $path = "$entity_type/%$entity_type"; - $info['menu_base_path'] = $path; - } + $path = $info['menu_base_path']; - $path = $info['menu_base_path']; + $info += array( + 'menu_view_path' => $path, + 'menu_edit_path' => "$path/edit", + 'menu_path_wildcard' => "%$entity_type", + ); - $info += array( - 'menu_view_path' => $path, - 'menu_edit_path' => "$path/edit", - 'menu_path_wildcard' => "%$entity_type", - ); + $entity_position = count(explode('/', $path)) - 1; + $info['translation']['translation_entity'] += array( + 'access_callback' => 'translation_entity_translate_access', + 'access_arguments' => array($entity_position), + ); + } +} - $entity_position = count(explode('/', $path)) - 1; - $info['translation']['translation_entity'] += array( - 'access_callback' => 'translation_entity_translate_access', - 'access_arguments' => array($entity_position), - ); +/** + * Implements hook_entity_bundle_info_alter(). + */ +function translation_entity_entity_bundle_info_alter(&$bundles) { + foreach ($bundles as $entity_type => &$info) { + foreach ($info as $bundle => &$bundle_info) { + $enabled = translation_entity_get_config($entity_type, $bundle, 'enabled'); + $bundle_info['translatable'] = !empty($enabled); } } } @@ -279,9 +278,7 @@ function _translation_entity_menu_strip_loaders($path) { */ function translation_entity_translate_access(EntityInterface $entity) { $entity_type = $entity->entityType(); - return empty($entity->language()->locked) && - language_multilingual() && - translation_entity_enabled($entity_type, $entity->bundle()) && + return empty($entity->language()->locked) && language_multilingual() && $entity->isTranslatable() && (user_access('create entity translations') || user_access('update entity translations') || user_access('delete entity translations')); } @@ -445,26 +442,26 @@ function translation_entity_set_config($entity_type, $bundle, $setting, $value) * @param string $bundle * (optional) The bundle of the entity. If no bundle is provided, all the * available bundles are checked. - * @param boolean $skip_handler - * (optional) Specifies whether the availablity of a field translation handler - * should affect the returned value. By default the check is performed. * * @returns * TRUE if the specified bundle is translatable. If no bundle is provided * returns TRUE if at least one of the entity bundles is translatable. */ -function translation_entity_enabled($entity_type, $bundle = NULL, $skip_handler = FALSE) { +function translation_entity_enabled($entity_type, $bundle = NULL) { $enabled = FALSE; - $bundles = !empty($bundle) ? array($bundle) : array_keys(entity_get_bundles($entity_type)); + $info = entity_get_info($entity_type); - foreach ($bundles as $bundle) { - if (translation_entity_get_config($entity_type, $bundle, 'enabled')) { - $enabled = TRUE; - break; + if (!empty($info['translatable'])) { + $bundles = !empty($bundle) ? array($bundle) : array_keys(entity_get_bundles($entity_type)); + foreach ($bundles as $bundle) { + if (translation_entity_get_config($entity_type, $bundle, 'enabled')) { + $enabled = TRUE; + break; + } } } - return $enabled && ($skip_handler || field_has_translation_handler($entity_type, 'translation_entity')); + return $enabled; } /** @@ -602,7 +599,7 @@ function translation_entity_permission() { * Implements hook_form_alter(). */ function translation_entity_form_alter(array &$form, array &$form_state) { - if (($form_controller = translation_entity_form_controller($form_state)) && ($entity = $form_controller->getEntity($form_state)) && !$entity->isNew() && translation_entity_enabled($entity->entityType(), $entity->bundle())) { + if (($form_controller = translation_entity_form_controller($form_state)) && ($entity = $form_controller->getEntity($form_state)) && !$entity->isNew() && $entity->isTranslatable()) { $controller = translation_entity_controller($entity->entityType()); $controller->entityFormAlter($form, $form_state, $entity); @@ -643,7 +640,7 @@ function translation_entity_field_language_alter(&$display_language, $context) { $entity = $context['entity']; $entity_type = $entity->entityType(); - if (isset($entity->translation[$context['langcode']]) && translation_entity_enabled($entity_type, $entity->bundle()) && !translation_entity_view_access($entity, $context['langcode'])) { + if (isset($entity->translation[$context['langcode']]) && $entity->isTranslatable() && !translation_entity_view_access($entity, $context['langcode'])) { $instances = field_info_instances($entity_type, $entity->bundle()); // Avoid altering the real entity. $entity = clone($entity); @@ -679,7 +676,7 @@ function translation_entity_entity_load(array $entities, $entity_type) { if (translation_entity_enabled($entity_type)) { foreach ($entities as $entity) { - if (translation_entity_enabled($entity_type, $entity->bundle())) { + if ($entity->isTranslatable()) { $enabled_entities[$entity->id()] = $entity; } } @@ -718,7 +715,7 @@ function translation_entity_load_translation_metadata(array $entities, $entity_t */ function translation_entity_entity_insert(EntityInterface $entity) { // Only do something if translation support for the given entity is enabled. - if (!translation_entity_enabled($entity->entityType(), $entity->bundle())) { + if (!$entity->isTranslatable()) { return; } @@ -758,7 +755,7 @@ function translation_entity_entity_insert(EntityInterface $entity) { */ function translation_entity_entity_delete(EntityInterface $entity) { // Only do something if translation support for the given entity is enabled. - if (!translation_entity_enabled($entity->entityType(), $entity->bundle())) { + if (!$entity->isTranslatable()) { return; } @@ -773,7 +770,7 @@ function translation_entity_entity_delete(EntityInterface $entity) { */ function translation_entity_entity_update(EntityInterface $entity) { // Only do something if translation support for the given entity is enabled. - if (!translation_entity_enabled($entity->entityType(), $entity->bundle())) { + if (!$entity->isTranslatable()) { return; } @@ -867,7 +864,7 @@ function translation_entity_field_info_alter(&$info) { * Implements hook_field_attach_presave(). */ function translation_entity_field_attach_presave(EntityInterface $entity) { - if (translation_entity_enabled($entity->entityType(), $entity->bundle())) { + if ($entity->isTranslatable()) { $attributes = drupal_container()->get('request')->attributes; Drupal::service('translation_entity.synchronizer')->synchronizeFields($entity, $attributes->get('working_langcode'), $attributes->get('source_langcode')); } diff --git a/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/User.php b/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/User.php index 64afbfc..fb7cbbb 100644 --- a/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/User.php +++ b/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/User.php @@ -31,6 +31,7 @@ * uri_callback = "user_uri", * label_callback = "user_label", * fieldable = TRUE, + * translatable = TRUE, * entity_keys = { * "id" = "uid", * "uuid" = "uuid" diff --git a/core/modules/views/views_ui/lib/Drupal/views_ui/ViewUI.php b/core/modules/views/views_ui/lib/Drupal/views_ui/ViewUI.php index 8c44bb0..eae95fc 100644 --- a/core/modules/views/views_ui/lib/Drupal/views_ui/ViewUI.php +++ b/core/modules/views/views_ui/lib/Drupal/views_ui/ViewUI.php @@ -985,6 +985,13 @@ public function getOriginalEntity() { } /** + * Implements \Drupal\Core\Entity\EntityInterface::isTranslatable(). + */ + public function isTranslatable() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** * Implements \Drupal\Core\TypedData\ContextAwareInterface::getName(). */ public function getName() {