diff --git a/core/includes/language.inc b/core/includes/language.inc index 896cf69..bd57445 100644 --- a/core/includes/language.inc +++ b/core/includes/language.inc @@ -531,11 +531,14 @@ function language_url_split_prefix($path, $languages) { * * @param * (optional) The language type. Defaults to Language::TYPE_CONTENT. + * @param + * (optional) An associative arra of arbitrary data that can be useful to hook + * implementations to determine the proper fallback chain. * * @return * An array of language codes. */ -function language_fallback_get_candidates($type = Language::TYPE_CONTENT) { +function language_fallback_get_candidates($type = Language::TYPE_CONTENT, $context = array()) { $fallback_candidates = &drupal_static(__FUNCTION__); if (!isset($fallback_candidates)) { @@ -544,7 +547,7 @@ function language_fallback_get_candidates($type = Language::TYPE_CONTENT) { $fallback_candidates[] = Language::LANGCODE_NOT_SPECIFIED; // Let other modules hook in and add/change candidates. - drupal_alter('language_fallback_candidates', $fallback_candidates); + drupal_alter('language_fallback_candidates', $fallback_candidates, $type, $context); } return $fallback_candidates; diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php index c9fc572..eb22db3 100644 --- a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php +++ b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Entity; use Drupal\Core\Language\Language; +use Drupal\Core\Language\LanguageManager; use PDO; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Entity\Query\QueryInterface; @@ -59,13 +60,21 @@ class DatabaseStorageController extends EntityStorageControllerBase { protected $database; /** + * The language manager service. + * + * @var \Drupal\Core\Language\LanguageManager + */ + protected $languageManager; + + /** * {@inheritdoc} */ public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) { return new static( $entity_type, $entity_info, - $container->get('database') + $container->get('database'), + $container->get('language_manager') ); } @@ -79,10 +88,11 @@ public static function createInstance(ContainerInterface $container, $entity_typ * @param \Drupal\Core\Database\Connection $database * The database connection to be used. */ - public function __construct($entity_type, array $entity_info, Connection $database) { + public function __construct($entity_type, array $entity_info, Connection $database, LanguageManager $languageManager) { parent::__construct($entity_type, $entity_info); $this->database = $database; + $this->languageManager = $languageManager; // Check if the entity type supports IDs. if (isset($this->entityInfo['entity_keys']['id'])) { @@ -359,6 +369,7 @@ public function create(array $values) { $entity_class::preCreate($this, $values); $entity = new $entity_class($values, $this->entityType); + $entity->setLanguageManager($this->languageManager); // Assign a new UUID if there is none yet. if ($this->uuidKey && !isset($entity->{$this->uuidKey})) { diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php index 911de6c..2009ddc 100644 --- a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php +++ b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Entity; use Drupal\Core\Language\Language; +use Drupal\Core\Language\LanguageManager; use PDO; use Drupal\Core\Entity\Query\QueryInterface; @@ -53,8 +54,8 @@ class DatabaseStorageControllerNG extends DatabaseStorageController { /** * Overrides DatabaseStorageController::__construct(). */ - public function __construct($entity_type, array $entity_info, Connection $database) { - parent::__construct($entity_type,$entity_info, $database); + public function __construct($entity_type, array $entity_info, Connection $database, LanguageManager $languageManager) { + parent::__construct($entity_type,$entity_info, $database, $languageManager); $this->bundleKey = !empty($this->entityInfo['entity_keys']['bundle']) ? $this->entityInfo['entity_keys']['bundle'] : FALSE; $this->entityClass = $this->entityInfo['class']; @@ -109,6 +110,7 @@ public function create(array $values) { $bundle = $values[$this->bundleKey]; } $entity = new $this->entityClass(array(), $this->entityType, $bundle); + $entity->setLanguageManager($this->languageManager); foreach ($entity as $name => $field) { if (isset($values[$name])) { @@ -248,6 +250,7 @@ protected function mapFromStorageRecords(array $records, $load_revision = FALSE) $bundle = $this->bundleKey ? $record->{$this->bundleKey} : FALSE; // Turn the record into an entity class. $entities[$id] = new $this->entityClass($entities[$id], $this->entityType, $bundle); + $entities[$id]->setLanguageManager($this->languageManager); } } $this->attachPropertyData($entities, $load_revision); @@ -319,6 +322,7 @@ protected function attachPropertyData(array &$entities, $revision_id = FALSE) { $bundle = $this->bundleKey ? $values[$this->bundleKey][Language::LANGCODE_DEFAULT] : FALSE; // Turn the record into an entity class. $entities[$id] = new $this->entityClass($values, $this->entityType, $bundle, array_keys($translations[$id])); + $entities[$id]->setLanguageManager($this->languageManager); } } } diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php index 5fad856..76b8a33 100644 --- a/core/lib/Drupal/Core/Entity/Entity.php +++ b/core/lib/Drupal/Core/Entity/Entity.php @@ -9,6 +9,7 @@ use Drupal\Component\Uuid\Uuid; use Drupal\Core\Language\Language; +use Drupal\Core\Language\LanguageManager; use Drupal\Core\TypedData\TranslatableInterface; use Drupal\Core\TypedData\TypedDataInterface; use Drupal\user\UserInterface; @@ -61,6 +62,14 @@ class Entity implements IteratorAggregate, EntityInterface { protected $isDefaultRevision = TRUE; /** + * The language manager to be used to retrieve languages and retrieve the + * current content language. + * + * @var \Drupal\Core\Language\LanguageManager + */ + protected $languageManager; + + /** * Constructs an Entity object. * * @param array $values @@ -121,6 +130,13 @@ public function setNewRevision($value = TRUE) { } /** + * {@inheritdoc} + */ + public function setLanguageManager(LanguageManager $languageManager) { + $this->languageManager = $languageManager; + } + + /** * Implements \Drupal\Core\Entity\EntityInterface::entityType(). */ public function entityType() { @@ -305,6 +321,17 @@ public function getTranslation($langcode) { } /** + * Implements \Drupal\Core\TypedData\TranslatableInterface::getTranslation(). + * + * @return \Drupal\Core\Entity\EntityInterface + */ + public function getCurrentTranslation($langcode = NULL, LanguageManager $languageManager = NULL) { + // @todo: Replace by EntityNG implementation once all entity types have been + // converted to use the entity field API. + return $this; + } + + /** * Returns the languages the entity is translated to. * * @todo: Remove once all entity types implement the entity field API. diff --git a/core/lib/Drupal/Core/Entity/EntityBCDecorator.php b/core/lib/Drupal/Core/Entity/EntityBCDecorator.php index b84634a..5988251 100644 --- a/core/lib/Drupal/Core/Entity/EntityBCDecorator.php +++ b/core/lib/Drupal/Core/Entity/EntityBCDecorator.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Entity; use Drupal\Core\Language\Language; +use Drupal\Core\Language\LanguageManager; use IteratorAggregate; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\TypedData\TypedDataInterface; @@ -268,6 +269,13 @@ public function setPropertyValues($values) { } /** + * {@inheritdoc} + */ + public function setLanguageManager(LanguageManager $languageManager) { + $this->decorated->setLanguageManager($languageManager); + } + + /** * Forwards the call to the decorated entity. */ public function getPropertyDefinition($name) { @@ -429,6 +437,13 @@ public function getTranslation($langcode) { } /** + * {@inheritdoc} + */ + public function getCurrentTranslation($langcode = NULL, LanguageManager $languageManager = NULL) { + return $this->decorated->getCurrentTranslation($languageManager); + } + + /** * Forwards the call to the decorated entity. */ public function getType() { diff --git a/core/lib/Drupal/Core/Entity/EntityInterface.php b/core/lib/Drupal/Core/Entity/EntityInterface.php index b8fe82b..8822778 100644 --- a/core/lib/Drupal/Core/Entity/EntityInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityInterface.php @@ -7,6 +7,7 @@ namespace Drupal\Core\Entity; +use Drupal\Core\Language\LanguageManager; use Drupal\Core\TypedData\AccessibleInterface; use Drupal\Core\TypedData\ComplexDataInterface; use Drupal\Core\TypedData\TranslatableInterface; @@ -83,6 +84,14 @@ public function isNewRevision(); public function setNewRevision($value = TRUE); /** + * Sets the language manager to be used when dealing with languages. + * + * @param \Drupal\Core\Language\LanguageManager + * The language manager. + */ + public function setLanguageManager(LanguageManager $languageManager); + + /** * Enforces an entity to be new. * * Allows migrations to create entities with pre-defined IDs by forcing the diff --git a/core/lib/Drupal/Core/Entity/EntityNG.php b/core/lib/Drupal/Core/Entity/EntityNG.php index a2f90d1..f811368 100644 --- a/core/lib/Drupal/Core/Entity/EntityNG.php +++ b/core/lib/Drupal/Core/Entity/EntityNG.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Entity; use Drupal\Core\Language\Language; +use Drupal\Core\Language\LanguageManager; use Drupal\Core\Session\AccountInterface; use Drupal\Core\TypedData\TypedDataInterface; use ArrayIterator; @@ -99,8 +100,6 @@ class EntityNG extends Entity { */ protected $uriPlaceholderReplacements; - - /** * Language code identifying the entity active language. * @@ -521,8 +520,37 @@ public function getTranslation($langcode) { } if (empty($translation)) { - $message = 'Invalid translation language (@langcode) specified.'; - throw new \InvalidArgumentException(format_string($message, array('@langcode' => $langcode))); + throw new \InvalidArgumentException("Invalid '$langcode' specified."); + } + + return $translation; + } + + /** + * {@inheritdoc} + */ + public function getCurrentTranslation($langcode = NULL, LanguageManager $languageManager = NULL) { + $translation = $this; + + $languageManager = isset($languageManager) ? $languageManager : $this->languageManager; + if (!empty($languageManager) && !isset($langcode)) { + $langcode = $languageManager->getLanguage(Language::TYPE_CONTENT)->langcode; + } + + if (!empty($langcode)) { + if ($this->hasTranslation($langcode)) { + $translation = $this->getTranslation($langcode); + } + else { + $context = array('entity' => $this); + $candidates = language_fallback_get_candidates(Language::TYPE_CONTENT, $context); + foreach (array_diff($candidates, array($langcode)) as $candidate) { + if ($this->hasTranslation($candidate)) { + $translation = $this->getTranslation($candidate); + break; + } + } + } } return $translation; diff --git a/core/lib/Drupal/Core/TypedData/TranslatableInterface.php b/core/lib/Drupal/Core/TypedData/TranslatableInterface.php index 5104271..53b0571 100644 --- a/core/lib/Drupal/Core/TypedData/TranslatableInterface.php +++ b/core/lib/Drupal/Core/TypedData/TranslatableInterface.php @@ -7,6 +7,8 @@ namespace Drupal\Core\TypedData; +use Drupal\Core\Language\LanguageManager; + /** * Interface for translatable data. */ @@ -49,6 +51,25 @@ public function getTranslationLanguages($include_default = TRUE); */ public function getTranslation($langcode); + /** + * Returns the translation to be used in the current context. + * + * This will check whether a translation for the requested langauge is + * available and if it is not, it will fall back to the most appropriate + * translation based on the available context provided by the language + * manager. + * + * @param $language + * (optional) The language code of the translation to be retrieved. Defaults + * to the current language. + * @param \Drupal\Core\Language\LanguageManager $languageManager + * (optional) The language manager to be used to determine the current + * content language. Defaults to the injected one. + * + * @return \Drupal\Core\TypedData\TypedDataInterface + * A typed data object for the translated data. + */ + public function getCurrentTranslation($langcode = NULL, LanguageManager $languageManager = NULL); /** * Returns the entity object referring to the original language. diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php index 90fbdbb..f60fca8 100644 --- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php +++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php @@ -11,6 +11,7 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityStorageException; use Drupal\Core\Database\Connection; +use Drupal\Core\Language\LanguageManager; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Cmf\Component\Routing\RouteProviderInterface; @@ -55,8 +56,8 @@ class MenuLinkStorageController extends DatabaseStorageController implements Men * @param \Symfony\Cmf\Component\Routing\RouteProviderInterface $route_provider * The route provider service. */ - public function __construct($entity_type, array $entity_info, Connection $database, RouteProviderInterface $route_provider) { - parent::__construct($entity_type, $entity_info, $database); + public function __construct($entity_type, array $entity_info, Connection $database, LanguageManager $language_manager, RouteProviderInterface $route_provider) { + parent::__construct($entity_type, $entity_info, $database, $language_manager); $this->routeProvider = $route_provider; @@ -85,6 +86,7 @@ public static function createInstance(ContainerInterface $container, $entity_typ $entity_type, $entity_info, $container->get('database'), + $container->get('language_manager'), $container->get('router.route_provider') ); } diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityTranslationTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityTranslationTest.php index 6122822..3590422 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityTranslationTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityTranslationTest.php @@ -461,6 +461,12 @@ function testEntityTranslationAPI() { $instance->save(); $translation = $entity->addTranslation($langcode2); $this->assertEqual($translation->get($this->field_name)->value, $this->field_name . '_' . $langcode2, 'Language-aware default values correctly populated.'); + + // Check that retrieveing the current translation works as expected. + $entity = $this->reloadEntity($entity); + $language_manager = $this->container->get('language_manager'); + $translation = $entity->getCurrentTranslation($langcode2, $language_manager); + $this->assertEqual($translation->language()->langcode, $langcode, 'The current translation language matches the expected one.'); } } diff --git a/core/modules/user/lib/Drupal/user/UserStorageController.php b/core/modules/user/lib/Drupal/user/UserStorageController.php index 8b4653c..7e6fe4a 100644 --- a/core/modules/user/lib/Drupal/user/UserStorageController.php +++ b/core/modules/user/lib/Drupal/user/UserStorageController.php @@ -7,6 +7,7 @@ namespace Drupal\user; +use Drupal\Core\Language\LanguageManager; use Drupal\Core\Entity\EntityBCDecorator; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Password\PasswordInterface; @@ -51,8 +52,8 @@ class UserStorageController extends DatabaseStorageControllerNG implements UserS * @param \Drupal\user\UserDataInterface $user_data * The user data service. */ - public function __construct($entity_type, $entity_info, Connection $database, PasswordInterface $password, UserDataInterface $user_data) { - parent::__construct($entity_type, $entity_info, $database); + public function __construct($entity_type, $entity_info, Connection $database, LanguageManager $languageManager, PasswordInterface $password, UserDataInterface $user_data) { + parent::__construct($entity_type, $entity_info, $database, $languageManager); $this->password = $password; $this->userData = $user_data; @@ -66,6 +67,7 @@ public static function createInstance(ContainerInterface $container, $entity_typ $entity_type, $entity_info, $container->get('database'), + $container->get('language_manager'), $container->get('password'), $container->get('user.data') ); diff --git a/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php b/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php index 48df936..79941c0 100644 --- a/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php +++ b/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php @@ -11,6 +11,7 @@ use Drupal\Core\Entity\EntityStorageControllerInterface; use Drupal\views\ViewExecutable; use Drupal\Core\Database\Database; +use Drupal\Core\Language\LanguageManager; use Drupal\Core\TypedData\TypedDataInterface; use Drupal\Core\Session\AccountInterface; use Drupal\views\Plugin\views\query\Sql; @@ -907,6 +908,13 @@ public function setNewRevision($value = TRUE) { } /** + * {@inheritdoc} + */ + public function setLanguageManager(LanguageManager $languageManager) { + $this->storage->setLanguageManager($languageManager); + } + + /** * Implements \Drupal\Core\Entity\EntityInterface::enforceIsNew(). */ public function enforceIsNew($value = TRUE) { @@ -929,6 +937,14 @@ public function getTranslation($langcode) { } /** + * {@inheritdoc} + */ + public function getCurrentTranslation($langcode = NULL, LanguageManager $languageManager = NULL) { + // @todo Revisit this once config entities are converted to NG. + return $this; + } + + /** * Implements \Drupal\Core\TypedData\TranslatableInterface::getTranslationLanguages(). */ public function getTranslationLanguages($include_default = TRUE) {