diff --git a/core/core.services.yml b/core/core.services.yml index bd0464c45a516c4b5bd7594104ee11455e8fa1e6..12f3a3c8a0711b4602efe40c791640b9c6e00102 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -218,7 +218,7 @@ services: arguments: ['@event_dispatcher', '@service_container', '@controller_resolver'] language_manager: class: Drupal\Core\Language\LanguageManager - arguments: ['@state', '@module_handler'] + arguments: ['@state'] string_translator.custom_strings: class: Drupal\Core\StringTranslation\Translator\CustomStrings arguments: ['@settings'] diff --git a/core/includes/language.inc b/core/includes/language.inc index 07cc38354215dea8c51b95ca26391362f47760e4..e057616c23f30b444e37fedb2f013e2cf3b33b0f 100644 --- a/core/includes/language.inc +++ b/core/includes/language.inc @@ -545,6 +545,30 @@ function language_url_split_prefix($path, $languages) { return array(FALSE, $path); } +/** + * Returns the possible fallback languages ordered by language weight. + * + * @param + * (optional) The language type. Defaults to Language::TYPE_CONTENT. + * + * @return + * An array of language codes. + */ +function language_fallback_get_candidates($type = Language::TYPE_CONTENT) { + $fallback_candidates = &drupal_static(__FUNCTION__); + + if (!isset($fallback_candidates)) { + // Get languages ordered by weight, add Language::LANGCODE_NOT_SPECIFIED at the end. + $fallback_candidates = array_keys(language_list()); + $fallback_candidates[] = Language::LANGCODE_NOT_SPECIFIED; + + // Let other modules hook in and add/change candidates. + drupal_alter('language_fallback_candidates', $fallback_candidates); + } + + return $fallback_candidates; +} + /** * @} End of "language_negotiation" */ diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php index 5447e90a3fec792fd81294b329f1236f56b84f7f..177a4952ca6cdc0eb283b765423c7a924f4870b2 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php @@ -423,12 +423,6 @@ protected function getTranslatedField($property_name, $langcode) { */ public function set($property_name, $value, $notify = TRUE) { $this->get($property_name)->setValue($value, FALSE); - - if ($property_name == 'langcode') { - // Avoid using unset as this unnecessarily triggers magic methods later - // on. - $this->language = NULL; - } } /** @@ -664,7 +658,6 @@ protected function initializeTranslation($langcode) { $translation->values = &$this->values; $translation->fields = &$this->fields; $translation->translations = &$this->translations; - $translation->enforceIsNew = &$this->enforceIsNew; $translation->translationInitialize = FALSE; return $translation; diff --git a/core/lib/Drupal/Core/Entity/ContentEntityFormController.php b/core/lib/Drupal/Core/Entity/ContentEntityFormController.php index 7f613bc85078b0a2f56e7cb2fb312d9970e48caa..2103fc2b938cb51152e042cf37e223096a128a9c 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityFormController.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityFormController.php @@ -8,7 +8,6 @@ namespace Drupal\Core\Entity; use Drupal\Core\Language\Language; -use Symfony\Component\DependencyInjection\ContainerInterface; /** * Entity form controller variant for content entity types. @@ -17,32 +16,6 @@ */ class ContentEntityFormController extends EntityFormController { - /** - * The entity manager. - * - * @var \Drupal\Core\Entity\EntityManagerInterface - */ - protected $entityManager; - - /** - * Constructs a ContentEntityFormController object. - * - * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager - * The entity manager. - */ - public function __construct(EntityManagerInterface $entity_manager) { - $this->entityManager = $entity_manager; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('entity.manager') - ); - } - /** * {@inheritdoc} */ @@ -65,7 +38,6 @@ public function form(array $form, array &$form_state) { * {@inheritdoc} */ public function validate(array $form, array &$form_state) { - $this->updateFormLangcode($form_state); $entity = $this->buildEntity($form, $form_state); $entity_type = $entity->entityType(); $entity_langcode = $entity->language()->id; @@ -101,22 +73,53 @@ public function validate(array $form, array &$form_state) { protected function init(array &$form_state) { // Ensure we act on the translation object corresponding to the current form // language. - $langcode = $this->getFormLangcode($form_state); - $this->entity = $this->entity->getTranslation($langcode); + $this->entity = $this->getTranslatedEntity($form_state); parent::init($form_state); } + /** + * Returns the translation object corresponding to the form language. + * + * @param array $form_state + * A keyed array containing the current state of the form. + */ + protected function getTranslatedEntity(array $form_state) { + $langcode = $this->getFormLangcode($form_state); + $translation = $this->entity->getTranslation($langcode); + // Ensure that the entity object is a BC entity if the original one is. + return $this->entity instanceof EntityBCDecorator ? $translation->getBCEntity() : $translation; + } + /** * {@inheritdoc} */ - public function getFormLangcode(array &$form_state) { - if (empty($form_state['langcode'])) { - // Imply a 'view' operation to ensure users edit entities in the same - // language they are displayed. This allows to keep contextual editing - // working also for multilingual entities. - $form_state['langcode'] = $this->entityManager->getTranslationFromContext($this->entity)->language()->id; + public function getFormLangcode(array $form_state) { + $entity = $this->entity; + if (!empty($form_state['langcode'])) { + $langcode = $form_state['langcode']; + } + else { + // If no form langcode was provided we default to the current content + // language and inspect existing translations to find a valid fallback, + // if any. + $translations = $entity->getTranslationLanguages(); + $languageManager = \Drupal::languageManager(); + $langcode = $languageManager->getLanguage(Language::TYPE_CONTENT)->id; + $fallback = $languageManager->isMultilingual() ? language_fallback_get_candidates() : array(); + while (!empty($langcode) && !isset($translations[$langcode])) { + $langcode = array_shift($fallback); + } + } + + // If the site is not multilingual or no translation for the given form + // language is available, fall back to the entity language. + if (!empty($langcode)) { + return $langcode; + } + else { + // If the entity is translatable, return the original language. + return $entity->getUntranslated()->language()->id; } - return $form_state['langcode']; } /** @@ -133,8 +136,8 @@ public function buildEntity(array $form, array &$form_state) { $entity = clone $this->entity; $entity_type = $entity->entityType(); $info = entity_get_info($entity_type); + // @todo Exploit the Field API to process the submitted entity fields. - // @todo Exploit the Entity Field API to process the submitted field values. // Copy top-level form values that are entity fields but not handled by // field API without changing existing entity fields that are not being // edited by this form. Values of fields handled by field API are copied @@ -160,5 +163,4 @@ public function buildEntity(array $form, array &$form_state) { } return $entity; } - } diff --git a/core/lib/Drupal/Core/Entity/EntityFormController.php b/core/lib/Drupal/Core/Entity/EntityFormController.php index 54ee489a70308af096cdfafe4e128213aa94d82b..52ac7d54389b5fc81654babd1002577a9e40cb21 100644 --- a/core/lib/Drupal/Core/Entity/EntityFormController.php +++ b/core/lib/Drupal/Core/Entity/EntityFormController.php @@ -269,7 +269,6 @@ protected function actions(array $form, array &$form_state) { * {@inheritdoc} */ public function validate(array $form, array &$form_state) { - $this->updateFormLangcode($form_state); // @todo Remove this. // Execute legacy global validation handlers. unset($form_state['validate_handlers']); @@ -293,6 +292,8 @@ public function validate(array $form, array &$form_state) { public function submit(array $form, array &$form_state) { // Remove button and internal Form API values from submitted values. form_state_values_clean($form_state); + + $this->updateFormLangcode($form_state); $this->entity = $this->buildEntity($form, $form_state); return $this->entity; } @@ -324,7 +325,7 @@ public function delete(array $form, array &$form_state) { /** * {@inheritdoc} */ - public function getFormLangcode(array &$form_state) { + public function getFormLangcode(array $form_state) { return $this->entity->language()->id; } diff --git a/core/lib/Drupal/Core/Entity/EntityFormControllerInterface.php b/core/lib/Drupal/Core/Entity/EntityFormControllerInterface.php index 6ad106bfec469c2bbf85741a7675b04f60f7d806..07d8af17b688bf1e123dd14f66cf4d55f1b2cb1d 100644 --- a/core/lib/Drupal/Core/Entity/EntityFormControllerInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityFormControllerInterface.php @@ -26,7 +26,7 @@ interface EntityFormControllerInterface extends BaseFormIdInterface { * @return string * The form language code. */ - public function getFormLangcode(array &$form_state); + public function getFormLangcode(array $form_state); /** * Checks whether the current form language matches the entity one. diff --git a/core/lib/Drupal/Core/Entity/EntityInterface.php b/core/lib/Drupal/Core/Entity/EntityInterface.php index f02bb1e784be73ac56972332cb6c8d7394421c08..5f84ac90f105f586285732921e4653ad6757c82b 100644 --- a/core/lib/Drupal/Core/Entity/EntityInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityInterface.php @@ -91,7 +91,7 @@ public function bundle(); * * @param $langcode * (optional) The language code of the language that should be used for - * getting the label. If set to NULL, the entity's active language is + * getting the label. If set to NULL, the entity's default language is * used. * * @return diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php index 5fc2d16f0983c5f7be284305c6b1c6f1558eec95..101c8b71d0aad87836a8dc6af0592683cc60ec0f 100644 --- a/core/lib/Drupal/Core/Entity/EntityManager.php +++ b/core/lib/Drupal/Core/Entity/EntityManager.php @@ -10,7 +10,6 @@ use Drupal\Component\Plugin\PluginManagerBase; use Drupal\Component\Plugin\Factory\DefaultFactory; use Drupal\Component\Utility\NestedArray; -use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Language\LanguageManager; use Drupal\Core\Language\Language; @@ -18,8 +17,8 @@ use Drupal\Core\Plugin\Discovery\CacheDecorator; use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery; use Drupal\Core\Plugin\Discovery\InfoHookDecorator; +use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\StringTranslation\TranslationInterface; -use Drupal\Core\TypedData\TranslatableInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -457,37 +456,4 @@ public function getEntityTypeLabels() { return $options; } - /** - * {@inheritdoc} - */ - public function getTranslationFromContext(EntityInterface $entity, $langcode = NULL, $context = array()) { - $translation = $entity; - - if ($entity instanceof TranslatableInterface) { - if (empty($langcode)) { - $langcode = $this->languageManager->getLanguage(Language::TYPE_CONTENT)->id; - } - - // Retrieve language fallback candidates to perform the entity language - // negotiation. - $context['data'] = $entity; - $context += array('operation' => 'entity_view'); - $candidates = $this->languageManager->getFallbackCandidates($langcode, $context); - - // Ensure the default language has the proper language code. - $default_language = $entity->getUntranslated()->language(); - $candidates[$default_language->id] = Language::LANGCODE_DEFAULT; - - // Return the most fitting entity translation. - foreach ($candidates as $candidate) { - if ($entity->hasTranslation($candidate)) { - $translation = $entity->getTranslation($candidate); - break; - } - } - } - - return $translation; - } - } diff --git a/core/lib/Drupal/Core/Entity/EntityManagerInterface.php b/core/lib/Drupal/Core/Entity/EntityManagerInterface.php index d561f530e602fd087f1b9d5e1625c59a0951ed77..0e898cb6d8c4f9319136d6cc6f2797bf36470992 100644 --- a/core/lib/Drupal/Core/Entity/EntityManagerInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityManagerInterface.php @@ -244,27 +244,4 @@ public function getForm(EntityInterface $entity, $operation = 'default', array $ */ public function getBundleInfo($entity_type); - /** - * Returns the entity translation to be used in the given context. - * - * This will check whether a translation for the desired language is available - * and if not, it will fall back to the most appropriate translation based on - * the provided context. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity whose translation will be returned. - * @param string $langcode - * (optional) The language of the current context. Defaults to the current - * content language. - * @param array $context - * (optional) An associative array of arbitrary data that can be useful to - * determine the proper fallback sequence. - * - * @return \Drupal\Core\Entity\EntityInterface - * An entity object for the translated data. - * - * @see \Drupal\Core\Language\LanguageManager::getFallbackCandidates() - */ - public function getTranslationFromContext(EntityInterface $entity, $langcode = NULL, $context = array()); - } diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php index 2680e6fd5986b51adcb44eabfa8f26084a76eff5..b79ca59b325e3ff3ef03c9f01ae11f8d42bc967b 100644 --- a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php +++ b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php @@ -7,15 +7,13 @@ namespace Drupal\Core\Entity; -use Drupal\Core\Entity\EntityManager; -use Drupal\Core\Language\Language; use Drupal\entity\Entity\EntityDisplay; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Drupal\Core\Language\Language; /** * Base class for entity view controllers. */ -class EntityViewBuilder implements EntityControllerInterface, EntityViewBuilderInterface { +class EntityViewBuilder implements EntityViewBuilderInterface { /** * The type of entities for which this controller is instantiated. @@ -33,13 +31,6 @@ class EntityViewBuilder implements EntityControllerInterface, EntityViewBuilderI */ protected $entityInfo; - /** - * The entity manager service. - * - * @var \Drupal\Core\Entity\EntityManagerInterface - */ - protected $entityManager; - /** * An array of view mode info for the type of entities for which this * controller is instantiated. @@ -58,30 +49,12 @@ class EntityViewBuilder implements EntityControllerInterface, EntityViewBuilderI */ protected $cacheBin = 'cache'; - /** - * Constructs a new EntityViewBuilder. - * - * @param string $entity_type - * The entity type. - * @param array $entity_info - * The entity information array. - * @param \Drupal\Core\Entity\EntityManager $entity_manager - * The entity manager service. - */ - public function __construct($entity_type, array $entity_info, EntityManager $entity_manager) { + public function __construct($entity_type) { $this->entityType = $entity_type; - $this->entityInfo = $entity_info; - $this->entityManager = $entity_manager; + $this->entityInfo = entity_get_info($entity_type); $this->viewModesInfo = entity_get_view_modes($entity_type); } - /** - * {@inheritdoc} - */ - public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) { - return new static($entity_type, $entity_info, $container->get('entity.manager')); - } - /** * {@inheritdoc} */ @@ -194,14 +167,9 @@ public function viewMultiple(array $entities = array(), $view_mode = 'full', $la $view_modes = array(); $displays = array(); $context = array('langcode' => $langcode); - foreach ($entities as $key => $entity) { + foreach ($entities as $entity) { $bundle = $entity->bundle(); - // Ensure that from now on we are dealing with the proper translation - // object. - $entity = $this->entityManager->getTranslationFromContext($entity, $langcode); - $entities[$key] = $entity; - // Allow modules to change the view mode. $entity_view_mode = $view_mode; drupal_alter('entity_view_mode', $entity_view_mode, $entity, $context); diff --git a/core/lib/Drupal/Core/Language/Language.php b/core/lib/Drupal/Core/Language/Language.php index b5ea7101f392d8c7b63136579583767f36ab27e6..7181b32841a1dfb3d8a37f1fdf1bdaee718a0818 100644 --- a/core/lib/Drupal/Core/Language/Language.php +++ b/core/lib/Drupal/Core/Language/Language.php @@ -153,7 +153,7 @@ public function extend($obj) { * @param array $languages * The array of language objects keyed by langcode. */ - public static function sort(&$languages) { + public static function sort($languages) { uasort($languages, function ($a, $b) { $a_weight = isset($a->weight) ? $a->weight : 0; $b_weight = isset($b->weight) ? $b->weight : 0; diff --git a/core/lib/Drupal/Core/Language/LanguageManager.php b/core/lib/Drupal/Core/Language/LanguageManager.php index 532d4049419eb2266646ec30d278782f090e4f0f..7bc716d34167d7d8415efdfb4fcad37d3f0e4624 100644 --- a/core/lib/Drupal/Core/Language/LanguageManager.php +++ b/core/lib/Drupal/Core/Language/LanguageManager.php @@ -7,10 +7,8 @@ namespace Drupal\Core\Language; -use Drupal\Component\Utility\MapArray; -use Drupal\Core\Extension\ModuleHandlerInterface; -use Drupal\Core\KeyValueStore\KeyValueStoreInterface; use Symfony\Component\HttpFoundation\Request; +use Drupal\Core\KeyValueStore\KeyValueStoreInterface; /** * Class responsible for initializing each language type. @@ -31,13 +29,6 @@ class LanguageManager { */ protected $state = NULL; - /** - * The module handler service. - * - * @var \Drupal\Core\Extension\ModuleHandlerInterface - */ - protected $moduleHandler; - /** * An array of language objects keyed by language type. * @@ -66,13 +57,10 @@ class LanguageManager { * Constructs an LanguageManager object. * * @param \Drupal\Core\KeyValueStore\KeyValueStoreInterface $state - * (optional) The state keyvalue store. Defaults to NULL. - * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler - * (optional) The module handler service. Defaults to NULL. + * The state keyvalue store. */ - public function __construct(KeyValueStoreInterface $state = NULL, ModuleHandlerInterface $module_handler = NULL) { + public function __construct(KeyValueStoreInterface $state = NULL) { $this->state = $state; - $this->moduleHandler = $module_handler; } /** @@ -173,53 +161,6 @@ public function isMultilingual() { return ($this->state->get('language_count') ?: 1) > 1; } - /** - * Returns the language fallback candidates for a given context. - * - * @param string $langcode - * (optional) The language of the current context. Defaults to NULL. - * @param array $context - * (optional) An associative array of data that can be useful to determine - * the fallback sequence. The following keys are used in core: - * - langcode: The desired language. - * - operation: The name of the operation indicating the context where - * language fallback is being applied, e.g. 'entity_view'. - * - data: An arbitrary data structure that makes sense in the provided - * context, e.g. an entity. - * - * @return array - * An array of language codes sorted by priority: first values should be - * tried first. - */ - public function getFallbackCandidates($langcode = NULL, array $context = array()) { - if ($this->isMultilingual()) { - // Get languages ordered by weight, add Language::LANGCODE_NOT_SPECIFIED at - // the end. - $candidates = array_keys(language_list()); - $candidates[] = Language::LANGCODE_NOT_SPECIFIED; - $candidates = MapArray::copyValuesToKeys($candidates); - - // The first candidate should always be the desired language if specified. - if (!empty($langcode)) { - $candidates = array($langcode => $langcode) + $candidates; - } - - // Let other modules hook in and add/change candidates. - $type = 'language_fallback_candidates'; - $types = array(); - if (!empty($context['operation'])) { - $types[] = $type . '_' . $context['operation']; - } - $types[] = $type; - $this->moduleHandler->alter($types, $candidates, $context); - } - else { - $candidates = array(Language::LANGCODE_DEFAULT); - } - - return $candidates; - } - /** * Returns an array of the available language types. * diff --git a/core/lib/Drupal/Core/TypedData/TranslatableInterface.php b/core/lib/Drupal/Core/TypedData/TranslatableInterface.php index af66bfee39c792a0ae5ea75a4d21826add96f0a8..f94aa0b14ee485c020057abff89fc0211b4c6524 100644 --- a/core/lib/Drupal/Core/TypedData/TranslatableInterface.php +++ b/core/lib/Drupal/Core/TypedData/TranslatableInterface.php @@ -7,8 +7,6 @@ namespace Drupal\Core\TypedData; -use Drupal\Core\Language\LanguageManager; - /** * Interface for translatable data. */ @@ -37,9 +35,10 @@ public function getTranslationLanguages($include_default = TRUE); /** * Gets a translation of the data. * - * The returned translation has to be of the same type than this typed data - * object. If the specified translation does not exist, a new one will be - * instantiated. + * The returned translation has to be implement the same typed data interfaces + * as this typed data object, excluding the TranslatableInterface. E.g., if + * this typed data object implements the ComplexDataInterface and + * AccessibleInterface, the translation object has to implement both as well. * * @param $langcode * The language code of the translation to get or Language::LANGCODE_DEFAULT @@ -50,6 +49,7 @@ public function getTranslationLanguages($include_default = TRUE); */ public function getTranslation($langcode); + /** * Returns the translatable object referring to the original language. * diff --git a/core/modules/aggregator/lib/Drupal/aggregator/FeedFormController.php b/core/modules/aggregator/lib/Drupal/aggregator/FeedFormController.php index d4761f5eab3ca5d02840357f439ddc7566a310c9..0ca5bef07cd37266a869c9fc154e2f13e9257fb4 100644 --- a/core/modules/aggregator/lib/Drupal/aggregator/FeedFormController.php +++ b/core/modules/aggregator/lib/Drupal/aggregator/FeedFormController.php @@ -9,7 +9,7 @@ use Drupal\Component\Utility\String; use Drupal\Core\Entity\ContentEntityFormController; -use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Entity\EntityStorageControllerInterface; use Drupal\Core\Language\Language; use Drupal\aggregator\CategoryStorageControllerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -19,6 +19,13 @@ */ class FeedFormController extends ContentEntityFormController { + /** + * The feed storage. + * + * @var \Drupal\Core\Entity\EntityStorageControllerInterface + */ + protected $feedStorageController; + /** * The category storage controller. * @@ -29,13 +36,13 @@ class FeedFormController extends ContentEntityFormController { /** * Constructs a FeedForm object. * - * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager - * The entity manager. + * @param \Drupal\Core\Entity\EntityStorageControllerInterface $feed_storage + * The feed storage. * @param \Drupal\aggregator\CategoryStorageControllerInterface $category_storage_controller * The category storage controller. */ - public function __construct(EntityManagerInterface $entity_manager, CategoryStorageControllerInterface $category_storage_controller) { - parent::__construct($entity_manager); + public function __construct(EntityStorageControllerInterface $feed_storage, CategoryStorageControllerInterface $category_storage_controller) { + $this->feedStorageController = $feed_storage; $this->categoryStorageController = $category_storage_controller; } @@ -44,7 +51,7 @@ public function __construct(EntityManagerInterface $entity_manager, CategoryStor */ public static function create(ContainerInterface $container) { return new static( - $container->get('entity.manager'), + $container->get('plugin.manager.entity')->getStorageController('aggregator_feed'), $container->get('aggregator.category.storage') ); } @@ -118,8 +125,7 @@ public function form(array $form, array &$form_state) { public function validate(array $form, array &$form_state) { $feed = $this->buildEntity($form, $form_state); // Check for duplicate titles. - $feed_storage_controller = $this->entityManager->getStorageController('aggregator_feed'); - $result = $feed_storage_controller->getFeedDuplicates($feed); + $result = $this->feedStorageController->getFeedDuplicates($feed); foreach ($result as $item) { if (strcasecmp($item->title, $feed->label()) == 0) { form_set_error('title', $this->t('A feed named %feed already exists. Enter a unique title.', array('%feed' => $feed->label()))); diff --git a/core/modules/book/lib/Drupal/book/Form/BookOutlineForm.php b/core/modules/book/lib/Drupal/book/Form/BookOutlineForm.php index 61ff1fc763f0cde48aacf4d18b87d1bf68222200..94d9b8a2806a5e963c97f8cb38e34932c79d617f 100644 --- a/core/modules/book/lib/Drupal/book/Form/BookOutlineForm.php +++ b/core/modules/book/lib/Drupal/book/Form/BookOutlineForm.php @@ -8,7 +8,6 @@ namespace Drupal\book\Form; use Drupal\Core\Entity\ContentEntityFormController; -use Drupal\Core\Entity\EntityManagerInterface; use Drupal\book\BookManager; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -33,25 +32,19 @@ class BookOutlineForm extends ContentEntityFormController { /** * Constructs a BookOutlineForm object. - * - * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager - * The entity manager. - * @param \Drupal\book\BookManager $book_manager - * The BookManager service. */ - public function __construct(EntityManagerInterface $entity_manager, BookManager $book_manager) { - parent::__construct($entity_manager); - $this->bookManager = $book_manager; + public function __construct(BookManager $bookManager) { + $this->bookManager = $bookManager; } /** - * {@inheritdoc} + * This method lets us inject the services this class needs. + * + * Only inject services that are actually needed. Which services + * are needed will vary by the controller. */ public static function create(ContainerInterface $container) { - return new static( - $container->get('entity.manager'), - $container->get('book.manager') - ); + return new static($container->get('book.manager')); } /** diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module index f6ab885ae26191ce288c83e9ae000deaa32c0ab8..8b28e76d33195b30a27d49e8de94f5af6ece167f 100644 --- a/core/modules/comment/comment.module +++ b/core/modules/comment/comment.module @@ -896,7 +896,7 @@ function comment_translation_configuration_element_submit($form, &$form_state) { $key = 'language_configuration'; $comment_form_state = array( 'content_translation' => array('key' => $key), - 'language' => array($key => array('entity_type' => 'comment', 'bundle' => $form['#field']->name)), + 'language' => array($key => array('entity_type' => 'comment', 'bundle' => $form['#field']['name'])), 'values' => array($key => array('content_translation' => $form_state['values']['content_translation'])), ); content_translation_language_configuration_element_submit($form, $comment_form_state); diff --git a/core/modules/comment/lib/Drupal/comment/CommentFormController.php b/core/modules/comment/lib/Drupal/comment/CommentFormController.php index f0c38ad880d32830815cf15b7682cff597d3e9d1..db10747f4aa561981b282e8c08ed34929324482d 100644 --- a/core/modules/comment/lib/Drupal/comment/CommentFormController.php +++ b/core/modules/comment/lib/Drupal/comment/CommentFormController.php @@ -23,6 +23,13 @@ */ class CommentFormController extends ContentEntityFormController { + /** + * The entity manager service. + * + * @var \Drupal\Core\Entity\EntityManagerInterface + */ + protected $entityManager; + /** * The field info service. * @@ -51,28 +58,13 @@ public static function create(ContainerInterface $container) { * @param \Drupal\Core\Session\AccountInterface $current_user * The current user. */ + public function __construct(EntityManagerInterface $entity_manager, FieldInfo $field_info, AccountInterface $current_user) { - parent::__construct($entity_manager); + $this->entityManager = $entity_manager; $this->fieldInfo = $field_info; $this->currentUser = $current_user; } - /** - * {@inheritdoc} - */ - protected function init(array &$form_state) { - $comment = $this->entity; - - // Make the comment inherit the current content language unless specifically - // set. - if ($comment->isNew()) { - $language_content = \Drupal::languageManager()->getLanguage(Language::TYPE_CONTENT); - $comment->langcode->value = $language_content->id; - } - - parent::init($form_state); - } - /** * Overrides Drupal\Core\Entity\EntityFormController::form(). */ @@ -215,6 +207,13 @@ public function form(array $form, array &$form_state) { '#value' => ($comment->id() ? !$comment->uid->target_id : $this->currentUser->isAnonymous()), ); + // Make the comment inherit the current content language unless specifically + // set. + if ($comment->isNew()) { + $language_content = language(Language::TYPE_CONTENT); + $comment->langcode->value = $language_content->id; + } + // Add internal comment properties. $original = $comment->getUntranslated(); foreach (array('cid', 'pid', 'entity_id', 'entity_type', 'field_id', 'uid', 'langcode') as $key) { diff --git a/core/modules/comment/lib/Drupal/comment/CommentViewBuilder.php b/core/modules/comment/lib/Drupal/comment/CommentViewBuilder.php index d79363b611e881d2cea65ef62e509b10dfed198c..22dfb095971917f2cd3047ef8452a6363875704f 100644 --- a/core/modules/comment/lib/Drupal/comment/CommentViewBuilder.php +++ b/core/modules/comment/lib/Drupal/comment/CommentViewBuilder.php @@ -23,6 +23,13 @@ */ class CommentViewBuilder extends EntityViewBuilder implements EntityViewBuilderInterface, EntityControllerInterface { + /** + * The entity manager service. + * + * @var \Drupal\Core\Entity\EntityManagerInterface + */ + protected $entityManager; + /** * The field info service. * @@ -50,7 +57,6 @@ class CommentViewBuilder extends EntityViewBuilder implements EntityViewBuilderI public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) { return new static( $entity_type, - $entity_info, $container->get('entity.manager'), $container->get('field.info'), $container->get('module_handler'), @@ -63,8 +69,6 @@ public static function createInstance(ContainerInterface $container, $entity_typ * * @param string $entity_type * The entity type. - * @param array $entity_info - * The entity information array. * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager * The entity manager service. * @param \Drupal\field\FieldInfo $field_info @@ -74,8 +78,9 @@ public static function createInstance(ContainerInterface $container, $entity_typ * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token * The CSRF token manager service. */ - public function __construct($entity_type, array $entity_info, EntityManagerInterface $entity_manager, FieldInfo $field_info, ModuleHandlerInterface $module_handler, CsrfTokenGenerator $csrf_token) { - parent::__construct($entity_type, $entity_info, $entity_manager); + public function __construct($entity_type, EntityManagerInterface $entity_manager, FieldInfo $field_info, ModuleHandlerInterface $module_handler, CsrfTokenGenerator $csrf_token) { + parent::__construct($entity_type); + $this->entityManager = $entity_manager; $this->fieldInfo = $field_info; $this->moduleHandler = $module_handler; $this->csrfToken = $csrf_token; diff --git a/core/modules/comment/lib/Drupal/comment/Form/DeleteForm.php b/core/modules/comment/lib/Drupal/comment/Form/DeleteForm.php index 60b20369c0cc8beee8899cba8284b86fb32bde76..07f3146e6156e1528271462796e48b2eae18da44 100644 --- a/core/modules/comment/lib/Drupal/comment/Form/DeleteForm.php +++ b/core/modules/comment/lib/Drupal/comment/Form/DeleteForm.php @@ -10,7 +10,6 @@ use Drupal\comment\CommentManagerInterface; use Drupal\Core\Cache\Cache; use Drupal\Core\Entity\ContentEntityConfirmFormBase; -use Drupal\Core\Entity\EntityManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -28,13 +27,10 @@ class DeleteForm extends ContentEntityConfirmFormBase { /** * Constructs a DeleteForm object. * - * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager - * The entity manager. * @param \Drupal\comment\CommentManagerInterface $comment_manager * The comment manager service. */ - public function __construct(EntityManagerInterface $entity_manager, CommentManagerInterface $comment_manager) { - parent::__construct($entity_manager); + public function __construct(CommentManagerInterface $comment_manager) { $this->commentManager = $comment_manager; } @@ -43,7 +39,6 @@ public function __construct(EntityManagerInterface $entity_manager, CommentManag */ public static function create(ContainerInterface $container) { return new static( - $container->get('entity.manager'), $container->get('comment.manager') ); } diff --git a/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module index 2af6130334a08484c9af0c704df9677fc04f8dce..b31b8d8cb2bb917d6ef5a8d19aca56d2c7d63aac 100644 --- a/core/modules/content_translation/content_translation.module +++ b/core/modules/content_translation/content_translation.module @@ -323,12 +323,7 @@ function content_translation_translate_access(EntityInterface $entity) { */ function content_translation_view_access(EntityInterface $entity, $langcode, AccountInterface $account = NULL) { $entity_type = $entity->entityType(); - $info = $entity->entityInfo(); - $permission = "translate $entity_type"; - if (!empty($info['permission_granularity']) && $info['permission_granularity'] == 'bundle') { - $permission = "translate {$entity->bundle()} $entity_type"; - } - return !empty($entity->translation[$langcode]['status']) || user_access('translate any entity', $account) || user_access($permission, $account); + return !empty($entity->translation[$langcode]['status']) || user_access('translate any entity', $account) || user_access("translate $entity_type entities", $account); } /** @@ -632,10 +627,7 @@ function content_translation_permission() { * Implements hook_form_alter(). */ function content_translation_form_alter(array &$form, array &$form_state) { - $form_controller = content_translation_form_controller($form_state); - $entity = $form_controller ? $form_controller->getEntity() : NULL; - - if ($entity instanceof ContentEntityInterface && $entity->isTranslatable() && count($entity->getTranslationLanguages()) > 1) { + if (($form_controller = content_translation_form_controller($form_state)) && ($entity = $form_controller->getEntity()) && !$entity->isNew() && $entity instanceof ContentEntityInterface && $entity->isTranslatable()) { $controller = content_translation_controller($entity->entityType()); $controller->entityFormAlter($form, $form_state, $entity); @@ -658,16 +650,28 @@ function content_translation_form_alter(array &$form, array &$form_state) { } /** - * Implements hook_language_fallback_candidates_OPERATION_alter(). + * Implements hook_field_language_alter(). * * Performs language fallback for unaccessible translations. */ -function content_translation_language_fallback_candidates_entity_view_alter(&$candidates, $context) { - $entity = $context['data']; - foreach ($entity->getTranslationLanguages() as $langcode => $language) { - if (!content_translation_view_access($entity, $langcode)) { - unset($candidates[$langcode]); +function content_translation_field_language_alter(&$display_language, $context) { + $entity = $context['entity']; + $entity_type = $entity->entityType(); + + if ($entity instanceof ContentEntityInterface && isset($entity->translation[$context['langcode']]) && $entity->isTranslatable() && !content_translation_view_access($entity, $context['langcode'])) { + $instances = field_info_instances($entity_type, $entity->bundle()); + // Avoid altering the real entity. + $entity = clone($entity); + $entity_langcode = $entity->getUntranslated()->language()->id; + + foreach ($entity->translation as $langcode => $translation) { + if ($langcode == $context['langcode'] || !content_translation_view_access($entity, $langcode)) { + $entity->removeTranslation($langcode); + } } + + // Find the new fallback values. + field_language_fallback($display_language, $entity, $context['langcode']); } } diff --git a/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationUITest.php b/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationUITest.php index c24e064b6c031e461b6334028582c8b3e245bb30..21c84657b8c78ee1efed492aa5f4660514d4043d 100644 --- a/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationUITest.php +++ b/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationUITest.php @@ -34,18 +34,18 @@ abstract class ContentTranslationUITest extends ContentTranslationTestBase { * Tests the basic translation UI. */ function testTranslationUI() { - $this->doTestBasicTranslation(); + $this->assertBasicTranslation(); $this->doTestTranslationOverview(); - $this->doTestOutdatedStatus(); - $this->doTestPublishedStatus(); - $this->doTestAuthoringInfo(); - $this->doTestTranslationDeletion(); + $this->assertOutdatedStatus(); + $this->assertPublishedStatus(); + $this->assertAuthoringInfo(); + $this->assertTranslationDeletion(); } /** * Tests the basic translation workflow. */ - protected function doTestBasicTranslation() { + protected function assertBasicTranslation() { // Create a new test entity with original values in the default language. $default_langcode = $this->langcodes[0]; $values[$default_langcode] = $this->getNewEntityValues($default_langcode); @@ -117,7 +117,7 @@ protected function doTestTranslationOverview() { /** * Tests up-to-date status tracking. */ - protected function doTestOutdatedStatus() { + protected function assertOutdatedStatus() { $entity = entity_load($this->entityType, $this->entityId, TRUE); $langcode = 'fr'; $default_langcode = $this->langcodes[0]; @@ -150,7 +150,7 @@ protected function doTestOutdatedStatus() { /** * Tests the translation publishing status. */ - protected function doTestPublishedStatus() { + protected function assertPublishedStatus() { $entity = entity_load($this->entityType, $this->entityId, TRUE); $path = $this->controller->getEditPath($entity); @@ -172,7 +172,7 @@ protected function doTestPublishedStatus() { /** * Tests the translation authoring information. */ - protected function doTestAuthoringInfo() { + protected function assertAuthoringInfo() { $entity = entity_load($this->entityType, $this->entityId, TRUE); $path = $this->controller->getEditPath($entity); $values = array(); @@ -194,8 +194,8 @@ protected function doTestAuthoringInfo() { $entity = entity_load($this->entityType, $this->entityId, TRUE); foreach ($this->langcodes as $langcode) { - $this->assertEqual($entity->translation[$langcode]['uid'], $values[$langcode]['uid'], 'Translation author correctly stored.'); - $this->assertEqual($entity->translation[$langcode]['created'], $values[$langcode]['created'], 'Translation date correctly stored.'); + $this->assertEqual($entity->translation[$langcode]['uid'] == $values[$langcode]['uid'], 'Translation author correctly stored.'); + $this->assertEqual($entity->translation[$langcode]['created'] == $values[$langcode]['created'], 'Translation date correctly stored.'); } // Try to post non valid values and check that they are rejected. @@ -207,14 +207,14 @@ protected function doTestAuthoringInfo() { ); $this->drupalPostForm($path, $edit, $this->getFormSubmitAction($entity)); $this->assertTrue($this->xpath('//div[contains(@class, "error")]//ul'), 'Invalid values generate a list of form errors.'); - $this->assertEqual($entity->translation[$langcode]['uid'], $values[$langcode]['uid'], 'Translation author correctly kept.'); - $this->assertEqual($entity->translation[$langcode]['created'], $values[$langcode]['created'], 'Translation date correctly kept.'); + $this->assertEqual($entity->translation[$langcode]['uid'] == $values[$langcode]['uid'], 'Translation author correctly kept.'); + $this->assertEqual($entity->translation[$langcode]['created'] == $values[$langcode]['created'], 'Translation date correctly kept.'); } /** * Tests translation deletion. */ - protected function doTestTranslationDeletion() { + protected function assertTranslationDeletion() { // Confirm and delete a translation. $langcode = 'fr'; $entity = entity_load($this->entityType, $this->entityId, TRUE); diff --git a/core/modules/entity/lib/Drupal/entity/Tests/EntityDisplayModeTest.php b/core/modules/entity/lib/Drupal/entity/Tests/EntityDisplayModeTest.php index 29a46b5aff6eebce084664082e73a4b1f10aceee..803fbe3cc34100c8342b3bf38648b3a7899c2b88 100644 --- a/core/modules/entity/lib/Drupal/entity/Tests/EntityDisplayModeTest.php +++ b/core/modules/entity/lib/Drupal/entity/Tests/EntityDisplayModeTest.php @@ -40,11 +40,11 @@ public function testEntityViewModeUI() { $this->drupalGet('admin/structure/display-modes/view'); $this->assertResponse(200); - $this->drupalGet('admin/structure/display-modes/view/add/entity_test_mulrev'); + $this->drupalGet('admin/structure/display-modes/view/add/entity_test_mul'); $this->assertResponse(404); $this->drupalGet('admin/structure/display-modes/view/add'); - $this->assertNoLink(t('Test entity - revisions and data table'), 'An entity type with no view builder cannot have view modes.'); + $this->assertNoLink(t('Test entity - data table'), 'An entity type with no view builder cannot have view modes.'); // Test adding a view mode. $this->clickLink(t('Test entity')); diff --git a/core/modules/field/config/field.settings.yml b/core/modules/field/config/field.settings.yml index b6172c13a6984adb16b128babeb639584ced0514..0a9ebdffe1ce103df2ce3c721c2f37cedbc0ae1d 100644 --- a/core/modules/field/config/field.settings.yml +++ b/core/modules/field/config/field.settings.yml @@ -1 +1,2 @@ +language_fallback: true purge_batch_size: 10 diff --git a/core/modules/field/config/schema/field.schema.yml b/core/modules/field/config/schema/field.schema.yml index 424132b51c8489041883eca9c400c4b06ded0337..9996784750f450f379571bd5256a1baa33466b10 100644 --- a/core/modules/field/config/schema/field.schema.yml +++ b/core/modules/field/config/schema/field.schema.yml @@ -4,6 +4,9 @@ field.settings: type: mapping label: 'Field settings' mapping: + language_fallback: + type: boolean + label: 'Whether the field display falls back to global language fallback configuration' purge_batch_size: type: integer label: 'Maximum number of field data records to purge' diff --git a/core/modules/field/field.api.php b/core/modules/field/field.api.php index c252e2ed05a7435b2fa811daabb8e490fe2f87ce..b4f0198272f20d1e7fd2277b78bb57ccd4b1727c 100644 --- a/core/modules/field/field.api.php +++ b/core/modules/field/field.api.php @@ -414,6 +414,27 @@ function hook_field_attach_view_alter(&$output, $context) { } } +/** + * Perform alterations on field_language() values. + * + * This hook is invoked to alter the array of display language codes for the + * given entity. + * + * @param $display_langcode + * A reference to an array of language codes keyed by field name. + * @param $context + * An associative array containing: + * - entity: The entity with fields to render. + * - langcode: The language code $entity has to be displayed in. + */ +function hook_field_language_alter(&$display_langcode, $context) { + // Do not apply core language fallback rules if they are disabled or if Locale + // is not registered as a translation handler. + if (field_language_fallback_enabled() && field_has_translation_handler($context['entity']->entityType())) { + field_language_fallback($display_langcode, $context['entity'], $context['langcode']); + } +} + /** * Alter field_available_languages() values. * diff --git a/core/modules/field/field.attach.inc b/core/modules/field/field.attach.inc index 5567afd711eb210a184c6e66de5c73f169b66ddf..548cc7415a287361fba82143e8a296a78aeb6046 100644 --- a/core/modules/field/field.attach.inc +++ b/core/modules/field/field.attach.inc @@ -120,21 +120,19 @@ function field_invoke_method($method, $target_function, EntityInterface $entity, $langcodes = _field_language_suggestion($available_langcodes, $options['langcode'], $field_name); foreach ($langcodes as $langcode) { - if ($entity->hasTranslation($langcode)) { - $items = $entity->getTranslation($langcode)->get($field_name); - $items->filterEmptyValues(); + $items = $entity->getTranslation($langcode)->get($field_name); + $items->filterEmptyValues(); - $result = $target->$method($items, $a, $b); + $result = $target->$method($items, $a, $b); - if (isset($result)) { - // For methods with array results, we merge results together. - // For methods with scalar results, we collect results in an array. - if (is_array($result)) { - $return = array_merge($return, $result); - } - else { - $return[] = $result; - } + if (isset($result)) { + // For methods with array results, we merge results together. + // For methods with scalar results, we collect results in an array. + if (is_array($result)) { + $return = array_merge($return, $result); + } + else { + $return[] = $result; } } } @@ -225,12 +223,10 @@ function field_invoke_method_multiple($method, $target_function, array $entities $langcode = !empty($options['langcode'][$id]) ? $options['langcode'][$id] : $options['langcode']; $langcodes = _field_language_suggestion($available_langcodes, $langcode, $field_name); foreach ($langcodes as $langcode) { - if ($entity->hasTranslation($langcode)) { - // Group the items corresponding to the current field. - $items = $entity->getTranslation($langcode)->get($field_name); - $items->filterEmptyValues(); - $grouped_items[$instance_uuid][$langcode][$id] = $items; - } + // Group the items corresponding to the current field. + $items = $entity->getTranslation($langcode)->get($field_name); + $items->filterEmptyValues(); + $grouped_items[$instance_uuid][$langcode][$id] = $items; } } } diff --git a/core/modules/field/field.deprecated.inc b/core/modules/field/field.deprecated.inc index 8ac2bc2c7b76b46c73f40585421e3624d00ca176..b85380eddc2c25905a466fc9bc4a26ec7eeccb68 100644 --- a/core/modules/field/field.deprecated.inc +++ b/core/modules/field/field.deprecated.inc @@ -7,7 +7,6 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\ContentEntityInterface; -use Drupal\Core\Language\Language; use Drupal\entity\Entity\EntityDisplay; use Drupal\field\Field; @@ -859,80 +858,3 @@ function field_access($op, FieldInterface $field, $entity_type, $entity = NULL, $items = $entity ? $entity->get($field->id()) : NULL; return $access_controller->fieldAccess($op, $field, $account, $items); } - -/** - * Ensures that a given language code is valid. - * - * Checks whether the given language code is one of the enabled language codes. - * Otherwise, it returns the current, global language code; or the site's - * default language code, if the additional parameter $default is TRUE. - * - * @param $langcode - * The language code to validate. - * @param $default - * Whether to return the default language code or the current language code in - * case $langcode is invalid. - * - * @return - * A valid language code. - * - * @deprecated This has been deprecated in favor of the Entity Field API. - */ -function field_valid_language($langcode, $default = TRUE) { - $languages = field_content_languages(); - if (in_array($langcode, $languages)) { - return $langcode; - } - return $default ? language_default()->id : language(Language::TYPE_CONTENT)->id; -} - -/** - * Returns the display language code for the fields attached to the given - * entity. - * - * The actual language code for each given field is determined based on the - * requested language code and the actual data available in the fields - * themselves. - * If there is no registered translation handler for the given entity type, the - * display language code to be used is just Language::LANGCODE_NOT_SPECIFIED, as - * no other language code is allowed by field_available_languages(). - * - * If translation handlers are found, we let modules provide alternative display - * language codes for fields not having the requested language code available. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity to be displayed. - * @param $field_name - * (optional) The name of the field to be displayed. Defaults to NULL. If - * no value is specified, the display language codes for every field attached - * to the given entity will be returned. - * @param $langcode - * (optional) The language code $entity has to be displayed in. Defaults to - * NULL. If no value is given the current language will be used. - * - * @return - * A language code if a field name is specified, an array of language codes - * keyed by field name otherwise. - * - * @see \Drupal\Core\Language\LanguageManager::getFallbackCandidates() - * @see \Drupal\Core\Entity\EntityInterface::getFieldLangcode() - * - * @deprecated This has been deprecated in favor of the Entity Field API. - */ -function field_language(EntityInterface $entity, $field_name = NULL, $langcode = NULL) { - $langcode = \Drupal::entityManager()->getTranslationFromContext($entity, $langcode)->language()->id; - $definitions = $entity->getPropertyDefinitions(); - $translatable = field_has_translation_handler($entity->entityType()); - if (!isset($field_name)) { - $display_langcodes = array(); - foreach ($definitions as $name => $definition) { - if (!empty($definition['configurable'])) { - $display_langcodes[$name] = $translatable ? $langcode : Language::LANGCODE_NOT_SPECIFIED; - } - } - return $display_langcodes; - } - elseif (!empty($definitions[$field_name]['configurable'])) { - return $translatable ? $langcode : Language::LANGCODE_NOT_SPECIFIED; - } -} diff --git a/core/modules/field/field.install b/core/modules/field/field.install index 55d6ff492d0f23f563a153340f8973ce7d13ff1e..2da4aec72bf95d6ead0c4b185c9c1cd2a08d27ad 100644 --- a/core/modules/field/field.install +++ b/core/modules/field/field.install @@ -442,7 +442,10 @@ function field_update_8003() { * @ingroup config_upgrade */ function field_update_8004() { - // Do nothing: the former update code has been moved to locale_update_8018(). + update_variable_set('field_language_fallback', TRUE); + update_variables_to_config('field.settings', array( + 'field_language_fallback' => 'language_fallback', + )); } /** diff --git a/core/modules/field/field.multilingual.inc b/core/modules/field/field.multilingual.inc index 68493c7113d511899bde4b573a5cede11819d683..96ecabbf90a370da46c22d2dd9fbab1b5de23e17 100644 --- a/core/modules/field/field.multilingual.inc +++ b/core/modules/field/field.multilingual.inc @@ -65,6 +65,49 @@ */ +/** + * Applies language fallback rules to the fields attached to the given entity. + * + * Core language fallback rules simply check if fields have a field translation + * for the requested language code. If so, the requested language is returned, + * otherwise all the fallback candidates are inspected to see if there is a + * field translation available in another language. + * By default this is called by field_field_language_alter(), but this + * behavior can be disabled by setting the 'field.settings.language_fallback' + * variable to FALSE. + * + * @param $field_langcodes + * A reference to an array of language codes keyed by field name. + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity to be displayed. + * @param $langcode + * The language code $entity has to be displayed in. + */ +function field_language_fallback(&$field_langcodes, EntityInterface $entity, $langcode) { + // Lazily init fallback candidates to avoid unnecessary calls. + $fallback_candidates = NULL; + + foreach ($field_langcodes as $field_name => $field_langcode) { + // If the requested language is defined for the current field use it, + // otherwise search for a fallback value among the fallback candidates. + if (_field_translated_value_exists($entity, $langcode, $field_name)) { + $field_langcodes[$field_name] = $langcode; + } + else { + if (!isset($fallback_candidates)) { + require_once DRUPAL_ROOT . '/core/includes/language.inc'; + $fallback_candidates = language_fallback_get_candidates(); + } + foreach ($fallback_candidates as $fallback_langcode) { + if (_field_translated_value_exists($entity, $fallback_langcode, $field_name)) { + $field_langcodes[$field_name] = $fallback_langcode; + break; + } + } + } + } +} + /** * Collects the available language codes for the given entity type and field. * @@ -152,6 +195,13 @@ function field_content_languages() { return array_keys(language_list(Language::STATE_ALL)); } +/** + * Checks whether field language fallback is enabled. + */ +function field_language_fallback_enabled() { + return language_multilingual() && \Drupal::config('field.settings')->get('language_fallback'); +} + /** * Checks whether a field has language support. * @@ -192,3 +242,126 @@ function field_has_translation_handler($entity_type, $handler = NULL) { $info = entity_get_info($entity_type); return !empty($info['translatable']); } + +/** + * Ensures that a given language code is valid. + * + * Checks whether the given language code is one of the enabled language codes. + * Otherwise, it returns the current, global language code; or the site's + * default language code, if the additional parameter $default is TRUE. + * + * @param $langcode + * The language code to validate. + * @param $default + * Whether to return the default language code or the current language code in + * case $langcode is invalid. + * + * @return + * A valid language code. + */ +function field_valid_language($langcode, $default = TRUE) { + $languages = field_content_languages(); + if (in_array($langcode, $languages)) { + return $langcode; + } + return $default ? language_default()->id : language(Language::TYPE_CONTENT)->id; +} + +/** + * Returns the display language code for the fields attached to the given + * entity. + * + * The actual language code for each given field is determined based on the + * requested language code and the actual data available in the fields + * themselves. + * If there is no registered translation handler for the given entity type, the + * display language code to be used is just Language::LANGCODE_NOT_SPECIFIED, as no other + * language code is allowed by field_available_languages(). + * + * If translation handlers are found, we let modules provide alternative display + * language codes for fields not having the requested language code available. + * Core language fallback rules are provided by field_language_fallback() + * which is called by field_field_language_alter(). + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity to be displayed. + * @param $field_name + * (optional) The name of the field to be displayed. Defaults to NULL. If + * no value is specified, the display language codes for every field attached + * to the given entity will be returned. + * @param $langcode + * (optional) The language code $entity has to be displayed in. Defaults to + * NULL. If no value is given the current language will be used. + * + * @return + * A language code if a field name is specified, an array of language codes + * keyed by field name otherwise. + */ +function field_language(EntityInterface $entity, $displayed_field_name = NULL, $langcode = NULL) { + $display_langcodes = &drupal_static(__FUNCTION__, array()); + $id = $entity->id(); + $bundle = $entity->bundle(); + $entity_type = $entity->entityType(); + $langcode = field_valid_language($langcode, FALSE); + if (!isset($display_langcodes[$entity_type][$id][$langcode])) { + $display_langcode = array(); + + // By default, display language is set to one of the locked languages + // if the field translation is not available. It is up to translation + // handlers to implement language fallback rules. + foreach (field_info_instances($entity_type, $bundle) as $field_name => $instance) { + if (_field_translated_value_exists($entity, $langcode, $field_name)) { + $display_langcode[$field_name] = $langcode; + } + else { + // If the field has a value for one of the locked languages, then use + // that language for display. If not, the default one will be + // Language::LANGCODE_NOT_SPECIFIED. + $display_langcode[$field_name] = Language::LANGCODE_NOT_SPECIFIED; + foreach (language_list(Language::STATE_LOCKED) as $language_locked) { + if (isset($entity->{$field_name}[$language_locked->id])) { + $display_langcode[$field_name] = $language_locked->id; + break; + } + } + } + } + + if (field_has_translation_handler($entity_type)) { + $context = array( + 'entity' => $entity, + 'langcode' => $langcode, + ); + // Do not apply core language fallback rules if they are disabled or if + // the entity does not have a translation handler registered. + if (field_language_fallback_enabled() && field_has_translation_handler($entity_type)) { + field_language_fallback($display_langcode, $context['entity'], $context['langcode']); + } + drupal_alter('field_language', $display_langcode, $context); + } + + $display_langcodes[$entity_type][$id][$langcode] = $display_langcode; + } + + $display_langcode = $display_langcodes[$entity_type][$id][$langcode]; + + // Single-field mode. + if (isset($displayed_field_name)) { + return isset($display_langcode[$displayed_field_name]) ? $display_langcode[$displayed_field_name] : FALSE; + } + + return $display_langcode; +} + +/** + * Returns TRUE if a non-empty value exists for a given entity/language/field. + */ +function _field_translated_value_exists(EntityInterface $entity, $langcode, $field_name) { + if (!$entity->hasTranslation($langcode)) { + return FALSE; + } + $field = $entity->getTranslation($langcode)->$field_name; + $field->filterEmptyValues(); + $value = $field->getValue(); + return !empty($value); +} diff --git a/core/modules/field/lib/Drupal/field/Plugin/views/field/Field.php b/core/modules/field/lib/Drupal/field/Plugin/views/field/Field.php index f5228d0c66360f41b7065419e729dd986bf5afeb..d89340f61763b9b91087a5d89eca380761fa2a5e 100644 --- a/core/modules/field/lib/Drupal/field/Plugin/views/field/Field.php +++ b/core/modules/field/lib/Drupal/field/Plugin/views/field/Field.php @@ -263,7 +263,14 @@ public function query($use_groupby = FALSE) { $this->view->display_handler->options['field_langcode'] ); $placeholder = $this->placeholder(); - $langcode_fallback_candidates = $this->languageManager->getFallbackCandidates($langcode, array('operation' => 'views_query', 'data' => $this)); + $langcode_fallback_candidates = array($langcode); + if (field_language_fallback_enabled()) { + require_once DRUPAL_ROOT . '/includes/language.inc'; + $langcode_fallback_candidates = array_merge($langcode_fallback_candidates, language_fallback_get_candidates()); + } + else { + $langcode_fallback_candidates[] = Language::LANGCODE_NOT_SPECIFIED; + } $this->query->addWhereExpression(0, "$column IN($placeholder) OR $column IS NULL", array($placeholder => $langcode_fallback_candidates)); } } @@ -863,12 +870,11 @@ function field_langcode(EntityInterface $entity) { $this->view->display_handler->options['field_language'] ); - // Give the Entity Field API a chance to fallback to a different language - // (or Language::LANGCODE_NOT_SPECIFIED), in case the field has no data - // for the selected language. field_view_field() does this as well, but - // since the returned language code is used before calling it, the - // fallback needs to happen explicitly. - $langcode = $this->entityManager->getTranslationFromContext($entity, $langcode)->language()->id; + // Give the Field Language API a chance to fallback to a different language + // (or Language::LANGCODE_NOT_SPECIFIED), in case the field has no data for the selected language. + // field_view_field() does this as well, but since the returned language code + // is used before calling it, the fallback needs to happen explicitly. + $langcode = field_language($entity, $this->field_info['field_name'], $langcode); return $langcode; } diff --git a/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php b/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php index de2dd94ebcbeec3b99f7701fca41b01a5eb2ccbc..b381cf57c872bdfa8779bd4df59f89ccfd110208 100644 --- a/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php +++ b/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php @@ -80,8 +80,6 @@ public static function getInfo() { function setUp() { parent::setUp(); - $this->installConfig(array('language')); - $this->field_name = drupal_strtolower($this->randomName() . '_field_name'); $this->entity_type = 'entity_test'; @@ -161,7 +159,6 @@ function testTranslatableFieldSaveLoad() { $field_translations = array(); $available_langcodes = field_available_languages($entity_type, $this->field); $this->assertTrue(count($available_langcodes) > 1, 'Field is translatable.'); - $available_langcodes = array_keys(language_list()); $entity->langcode->value = reset($available_langcodes); foreach ($available_langcodes as $langcode) { $field_translations[$langcode] = $this->_generateTestFieldValues($this->field->getFieldCardinality()); @@ -231,4 +228,100 @@ function testTranslatableFieldSaveLoad() { } } + /** + * Tests display language logic for translatable fields. + */ + function testFieldDisplayLanguage() { + $field_name = drupal_strtolower($this->randomName() . '_field_name'); + $entity_type = 'entity_test'; + $bundle = 'entity_test'; + + // We need an additional field here to properly test display language + // suggestions. + $field = array( + 'name' => $field_name, + 'entity_type' => $entity_type, + 'type' => 'test_field', + 'cardinality' => 2, + 'translatable' => TRUE, + ); + entity_create('field_entity', $field)->save(); + + $instance = array( + 'field_name' => $field['name'], + 'entity_type' => $entity_type, + 'bundle' => $bundle, + ); + entity_create('field_instance', $instance)->save(); + + $enabled_langcodes = field_content_languages(); + $entity = entity_create($entity_type, array('id' => 1, 'revision_id' => 1, 'type' => $this->instance->bundle));; + $entity->langcode->value = reset($enabled_langcodes); + $instances = field_info_instances($entity_type, $bundle); + + $langcodes = array(); + // This array is used to store, for each field name, which one of the locked + // languages will be used for display. + $locked_languages = array(); + + // Generate field translations for languages different from the first + // enabled. + foreach ($instances as $instance) { + $field_name = $instance->getFieldName(); + $field = $instance->getField(); + do { + // Index 0 is reserved for the requested language, this way we ensure + // that no field is actually populated with it. + $langcode = $enabled_langcodes[mt_rand(1, count($enabled_langcodes) - 1)]; + } + while (isset($langcodes[$langcode])); + $langcodes[$langcode] = TRUE; + $entity->getTranslation($langcode)->{$field_name}->setValue($this->_generateTestFieldValues($field->getFieldCardinality())); + // If the langcode is one of the locked languages, then that one + // will also be used for display. Otherwise, the default one should be + // used, which is Language::LANGCODE_NOT_SPECIFIED. + if (language_is_locked($langcode)) { + $locked_languages[$field_name] = $langcode; + } + else { + $locked_languages[$field_name] = Language::LANGCODE_NOT_SPECIFIED; + } + } + + // Test multiple-fields display languages for untranslatable entities. + field_test_entity_info_translatable($entity_type, FALSE); + drupal_static_reset('field_language'); + $requested_langcode = $enabled_langcodes[0]; + $display_langcodes = field_language($entity, NULL, $requested_langcode); + foreach ($instances as $instance) { + $field_name = $instance->getFieldName(); + $this->assertTrue($display_langcodes[$field_name] == $locked_languages[$field_name], format_string('The display language for field %field_name is %language.', array('%field_name' => $field_name, '%language' => $locked_languages[$field_name]))); + } + + // Test multiple-fields display languages for translatable entities. + field_test_entity_info_translatable($entity_type, TRUE); + drupal_static_reset('field_language'); + $display_langcodes = field_language($entity, NULL, $requested_langcode); + foreach ($instances as $instance) { + $field_name = $instance->getFieldName(); + $langcode = $display_langcodes[$field_name]; + // As the requested language was not assinged to any field, if the + // returned language is defined for the current field, core fallback rules + // were successfully applied. + $this->assertTrue(!empty($entity->getTranslation($langcode)->{$field_name}) && $langcode != $requested_langcode, format_string('The display language for the field %field_name is %language.', array('%field_name' => $field_name, '%language' => $langcode))); + } + + // Test single-field display language. + drupal_static_reset('field_language'); + $langcode = field_language($entity, $this->field_name, $requested_langcode); + $this->assertTrue(!empty($entity->getTranslation($langcode)->{$this->field_name}) && $langcode != $requested_langcode, format_string('The display language for the (single) field %field_name is %language.', array('%field_name' => $field_name, '%language' => $langcode))); + + // Test field_language() basic behavior without language fallback. + \Drupal::state()->set('field_test.language_fallback', FALSE); + $entity->getTranslation($requested_langcode)->{$this->field_name}->value = mt_rand(1, 127); + drupal_static_reset('field_language'); + $display_langcode = field_language($entity, $this->field_name, $requested_langcode); + $this->assertEqual($display_langcode, $requested_langcode, 'Display language behave correctly when language fallback is disabled'); + } + } diff --git a/core/modules/field/lib/Drupal/field/Tests/TranslationWebTest.php b/core/modules/field/lib/Drupal/field/Tests/TranslationWebTest.php index 90d3ef7c592aecac5608da5fcabfc1885c7d89dd..8ddb73a14b17073766b0c5c9df976a77e38f6df6 100644 --- a/core/modules/field/lib/Drupal/field/Tests/TranslationWebTest.php +++ b/core/modules/field/lib/Drupal/field/Tests/TranslationWebTest.php @@ -109,7 +109,6 @@ function testFieldFormTranslationRevisions() { $field_name = $this->field->getFieldName(); // Store the field translations. - ksort($available_langcodes); $entity->langcode->value = key($available_langcodes); foreach ($available_langcodes as $langcode => $value) { $entity->getTranslation($langcode)->{$field_name}->value = $value + 1; diff --git a/core/modules/field/tests/modules/field_test/field_test.module b/core/modules/field/tests/modules/field_test/field_test.module index c02a40c6b7476f703519f705601891f8a0bf8d9a..992e6ef95e2d4d8893b4d0787ce547dd9e09bdb6 100644 --- a/core/modules/field/tests/modules/field_test/field_test.module +++ b/core/modules/field/tests/modules/field_test/field_test.module @@ -62,6 +62,15 @@ function field_test_field_available_languages_alter(&$langcodes, $context) { } } +/** + * Implements hook_field_language_alter(). + */ +function field_test_field_language_alter(&$display_langcode, $context) { + if (\Drupal::state()->get('field_test.language_fallback') ?: TRUE) { + field_language_fallback($display_langcode, $context['entity'], $context['langcode']); + } +} + /** * Store and retrieve keyed data for later verification by unit tests. * diff --git a/core/modules/forum/lib/Drupal/forum/Form/ForumFormController.php b/core/modules/forum/lib/Drupal/forum/Form/ForumFormController.php index 7b758ef48f7c3aa093fea4f461db9cfc9b56a71d..56bbb5169b7df6e6245471c86571975366e69bd1 100644 --- a/core/modules/forum/lib/Drupal/forum/Form/ForumFormController.php +++ b/core/modules/forum/lib/Drupal/forum/Form/ForumFormController.php @@ -8,7 +8,11 @@ namespace Drupal\forum\Form; use Drupal\Core\Cache\Cache; +use Drupal\Core\Config\ConfigFactory; use Drupal\taxonomy\TermFormController; +use Drupal\taxonomy\TermStorageControllerInterface; +use Drupal\taxonomy\VocabularyStorageControllerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Base form controller for forum term edit forms. @@ -29,6 +33,47 @@ class ForumFormController extends TermFormController { */ protected $urlStub = 'forum'; + /** + * The forum config. + * + * @var \Drupal\Core\Config\Config + */ + protected $config; + + /** + * Term Storage Controller. + * + * @var \Drupal\taxonomy\TermStorageControllerInterface + */ + protected $termStorage; + + /** + * Constructs a new ForumFormController object. + * + * @param \Drupal\taxonomy\VocabularyStorageControllerInterface $vocab_storage + * The vocabulary storage. + * @param \Drupal\Core\Config\ConfigFactory $config_factory + * The config factory service. + * @param \Drupal\taxonomy\TermStorageControllerInterface $term_storage + * The term storage. + */ + public function __construct(VocabularyStorageControllerInterface $vocab_storage, ConfigFactory $config_factory, TermStorageControllerInterface $term_storage) { + parent::__construct($vocab_storage, $config_factory); + $this->termStorage = $term_storage; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + $entity_manager = $container->get('entity.manager'); + return new static( + $entity_manager->getStorageController('taxonomy_vocabulary'), + $container->get('config.factory'), + $entity_manager->getStorageController('taxonomy_term') + ); + } + /** * {@inheritdoc} */ @@ -76,9 +121,8 @@ public function buildEntity(array $form, array &$form_state) { */ public function save(array $form, array &$form_state) { $term = $this->entity; - $term_storage = $this->entityManager->getStorageController('taxonomy_term'); - $status = $term_storage->save($term); + $status = $this->termStorage->save($term); switch ($status) { case SAVED_NEW: drupal_set_message($this->t('Created new @type %term.', array('%term' => $term->label(), '@type' => $this->forumFormType))); diff --git a/core/modules/language/language.api.php b/core/modules/language/language.api.php index d0a31c4992a903bcafc4cb7199956365f58ee328..cba3f966261c3f6201973894e1877fac9ce5c165 100644 --- a/core/modules/language/language.api.php +++ b/core/modules/language/language.api.php @@ -57,40 +57,6 @@ function hook_language_delete($language) { ->execute(); } -/** - * Allow modules to alter the language fallback candidates. - * - * @param array $candidates - * An array of language codes whose order will determine the language fallback - * order. - * @param array $context - * A language fallback context. - * - * @see \Drupal\Core\Language\LanguageManager::getFallbackCandidates() - */ -function hook_language_fallback_candidates_alter(array &$candidates, array $context) { - $candidates = array_reverse($candidates); -} - -/** - * Allow modules to alter the fallback candidates for specific operations. - * - * @param array $candidates - * An array of language codes whose order will determine the language fallback - * order. - * @param array $context - * A language fallback context. - * - * @see \Drupal\Core\Language\LanguageManager::getFallbackCandidates() - */ -function hook_language_fallback_candidates_OPERATION_alter(array &$candidates, array $context) { - // We know that the current OPERATION deals with entities so no need to check - // here. - if ($context['data']->entityType() == 'node') { - $candidates = array_reverse($candidates); - } -} - /** * @} End of "addtogroup hooks". */ diff --git a/core/modules/language/lib/Drupal/language/Tests/LanguageFallbackTest.php b/core/modules/language/lib/Drupal/language/Tests/LanguageFallbackTest.php deleted file mode 100644 index ba50def0cce576b707afb3cdc55cb921af91a1df..0000000000000000000000000000000000000000 --- a/core/modules/language/lib/Drupal/language/Tests/LanguageFallbackTest.php +++ /dev/null @@ -1,99 +0,0 @@ -<?php - -/** - * @file - * Contains \Drupal\language\Tests\LanguageFallbackTest. - */ - -namespace Drupal\language\Tests; - -use Drupal\Core\Language\Language; -use Drupal\simpletest\DrupalUnitTestBase; - -/** - * Tests the language fallback behavior. - */ -class LanguageFallbackTest extends DrupalUnitTestBase { - - public static function getInfo() { - return array( - 'name' => 'Language fallback', - 'description' => 'Tests the language fallback behavior.', - 'group' => 'Language', - ); - } - - /** - * The state storage service. - * - * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface - */ - protected $state; - - /** - * {@inheritdoc} - */ - protected function setUp() { - parent::setUp(); - - $this->enableModules(array('language', 'language_test')); - $this->installConfig(array('language')); - - $this->state = $this->container->get('state'); - - for ($i = 0; $i < 3; $i++) { - $language = new Language(); - $language->id = $this->randomName(2); - $language->weight = -$i; - language_save($language); - } - } - - /** - * Tests language fallback candidates. - */ - public function testCandidates() { - $manager = $this->getLanguageManager(); - $expected = array_keys(language_list() + array(Language::LANGCODE_NOT_SPECIFIED => NULL)); - - // Check that language fallback candidates by default are all the available - // languages sorted by weight. - $candidates = $manager->getFallbackCandidates(); - $this->assertEqual(array_values($candidates), $expected, 'Language fallback candidates are properly returned.'); - - // Check that candidates are alterable. - $this->state->set('language_test.fallback_alter.candidates', TRUE); - $expected = array_slice($expected, 0, count($expected) - 1); - $candidates = $manager->getFallbackCandidates(); - $this->assertEqual(array_values($candidates), $expected, 'Language fallback candidates are alterable.'); - - // Check that candidates are alterable for specific operations. - $this->state->set('language_test.fallback_alter.candidates', FALSE); - $this->state->set('language_test.fallback_operation_alter.candidates', TRUE); - $expected[] = Language::LANGCODE_NOT_SPECIFIED; - $expected[] = Language::LANGCODE_NOT_APPLICABLE; - $candidates = $manager->getFallbackCandidates(NULL, array('operation' => 'test')); - $this->assertEqual(array_values($candidates), $expected, 'Language fallback candidates are alterable for specific operations.'); - - // Check that when the site is monolingual no language fallback is applied. - $default_langcode = language_default()->id; - foreach (language_list() as $langcode => $language) { - if ($langcode != $default_langcode) { - language_delete($langcode); - } - } - $candidates = $this->getLanguageManager()->getFallbackCandidates(); - $this->assertEqual(array_values($candidates), array(Language::LANGCODE_DEFAULT), 'Language fallback is not applied when the Language module is not enabled.'); - } - - /** - * Returns the language manager service. - * - * @return \Drupal\Core\Language\LanguageManager - * The language manager. - */ - protected function getLanguageManager() { - return $this->container->get('language_manager'); - } - -} diff --git a/core/modules/language/tests/language_test/language_test.module b/core/modules/language/tests/language_test/language_test.module index 8e8cbaf97c2c13721eb87837eb605342a05aa90d..33c2b36b5766e59a29f1099c1f1f74581412daf8 100644 --- a/core/modules/language/tests/language_test/language_test.module +++ b/core/modules/language/tests/language_test/language_test.module @@ -109,22 +109,3 @@ function language_test_store_language_negotiation() { function language_test_language_negotiation_method($languages) { return 'it'; } - -/** - * Implements hook_language_fallback_candidates_alter(). - */ -function language_test_language_fallback_candidates_alter(array &$candidates, array $context) { - if (Drupal::state()->get('language_test.fallback_alter.candidates')) { - unset($candidates[Language::LANGCODE_NOT_SPECIFIED]); - } -} - -/** - * Implements hook_language_fallback_candidates_OPERATION_alter(). - */ -function language_test_language_fallback_candidates_test_alter(array &$candidates, array $context) { - if (Drupal::state()->get('language_test.fallback_operation_alter.candidates')) { - $langcode = Language::LANGCODE_NOT_APPLICABLE; - $candidates[$langcode] = $langcode; - } -} diff --git a/core/modules/locale/locale.install b/core/modules/locale/locale.install index c55e203c2173e89b86828a2fc23dfc4b053d1a80..3ab0247b2f6b88750a7b2254b0ceea651cead38f 100644 --- a/core/modules/locale/locale.install +++ b/core/modules/locale/locale.install @@ -964,15 +964,6 @@ function locale_update_8017() { )); } -/** - * Removes the field language fallback settings as it is no longer supported. - * - * @ingroup config_upgrade - */ -function locale_update_8018() { - update_variable_del('locale_field_language_fallback'); -} - /** * @} End of "addtogroup updates-7.x-to-8.x". * The next series of updates should start at 9000. diff --git a/core/modules/node/lib/Drupal/node/Controller/NodeController.php b/core/modules/node/lib/Drupal/node/Controller/NodeController.php index 4267101ffe1b3f39493730b144eedcb95d05ea3d..90e5899c86bf6f3e1e1c41a68c745d4937457ac6 100644 --- a/core/modules/node/lib/Drupal/node/Controller/NodeController.php +++ b/core/modules/node/lib/Drupal/node/Controller/NodeController.php @@ -127,7 +127,7 @@ public function page(NodeInterface $node) { * The page title. */ public function pageTitle(NodeInterface $node) { - return String::checkPlain($this->entityManager()->getTranslationFromContext($node)->label()); + return String::checkPlain($node->label()); } /** diff --git a/core/modules/node/lib/Drupal/node/Form/NodeDeleteForm.php b/core/modules/node/lib/Drupal/node/Form/NodeDeleteForm.php index 51da0371d34806ec6b9534397e2f3f0a394d7204..11eb3f31ecafbfa9574044585e2f98b5a7931665 100644 --- a/core/modules/node/lib/Drupal/node/Form/NodeDeleteForm.php +++ b/core/modules/node/lib/Drupal/node/Form/NodeDeleteForm.php @@ -9,7 +9,7 @@ use Drupal\Core\Cache\Cache; use Drupal\Core\Entity\ContentEntityConfirmFormBase; -use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Entity\EntityStorageControllerInterface; use Drupal\Core\Routing\UrlGeneratorInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -25,17 +25,24 @@ class NodeDeleteForm extends ContentEntityConfirmFormBase { */ protected $urlGenerator; + /** + * The node type storage. + * + * @var \Drupal\Core\Entity\EntityStorageControllerInterface + */ + protected $nodeTypeStorage; + /** * Constructs a NodeDeleteForm object. * - * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager - * The entity manager. * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator * The URL generator. + * @param \Drupal\Core\Entity\EntityStorageControllerInterface $node_type_storage + * The node type storage. */ - public function __construct(EntityManagerInterface $entity_manager, UrlGeneratorInterface $url_generator) { - parent::__construct($entity_manager); + public function __construct(UrlGeneratorInterface $url_generator, EntityStorageControllerInterface $node_type_storage) { $this->urlGenerator = $url_generator; + $this->nodeTypeStorage = $node_type_storage; } /** @@ -43,8 +50,8 @@ public function __construct(EntityManagerInterface $entity_manager, UrlGenerator */ public static function create(ContainerInterface $container) { return new static( - $container->get('entity.manager'), - $container->get('url_generator') + $container->get('url_generator'), + $container->get('entity.manager')->getStorageController('node_type') ); } @@ -87,8 +94,7 @@ public function getConfirmText() { public function submit(array $form, array &$form_state) { $this->entity->delete(); watchdog('content', '@type: deleted %title.', array('@type' => $this->entity->bundle(), '%title' => $this->entity->label())); - $node_type_storage = $this->entityManager->getStorageController('node_type'); - $node_type = $node_type_storage->load($this->entity->bundle())->label(); + $node_type = $this->nodeTypeStorage->load($this->entity->bundle())->label(); drupal_set_message(t('@type %title has been deleted.', array('@type' => $node_type, '%title' => $this->entity->label()))); Cache::invalidateTags(array('content' => TRUE)); $form_state['redirect'] = '<front>'; diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeTranslationUITest.php b/core/modules/node/lib/Drupal/node/Tests/NodeTranslationUITest.php index baa8d233dce622144b904b850d7a09540ca4dc0f..6a2608c1fe7551ecd0fa3a3f12f69168abe78d48 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeTranslationUITest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeTranslationUITest.php @@ -72,7 +72,7 @@ protected function getFormSubmitAction(EntityInterface $entity) { /** * Overrides \Drupal\content_translation\Tests\ContentTranslationUITest::assertPublishedStatus(). */ - protected function doTestPublishedStatus() { + protected function assertPublishedStatus() { $entity = entity_load($this->entityType, $this->entityId, TRUE); $path = $this->controller->getEditPath($entity); $languages = language_list(); @@ -104,7 +104,7 @@ protected function doTestPublishedStatus() { /** * Overrides \Drupal\content_translation\Tests\ContentTranslationUITest::assertAuthoringInfo(). */ - protected function doTestAuthoringInfo() { + protected function assertAuthoringInfo() { $entity = entity_load($this->entityType, $this->entityId, TRUE); $path = $this->controller->getEditPath($entity); $languages = language_list(); @@ -188,47 +188,4 @@ public function testDisabledBundle() { $this->assertEqual($enabledNode->id(), reset($rows)->entity_id); } - /** - * Tests that translations are rendered properly. - */ - function testTranslationRendering() { - $default_langcode = $this->langcodes[0]; - $values[$default_langcode] = $this->getNewEntityValues($default_langcode); - $this->entityId = $this->createEntity($values[$default_langcode], $default_langcode); - $node = \Drupal::entityManager()->getStorageController($this->entityType)->load($this->entityId); - $node->setPromoted(TRUE); - - // Create translations. - foreach (array_diff($this->langcodes, array($default_langcode)) as $langcode) { - $values[$langcode] = $this->getNewEntityValues($langcode); - $translation = $node->addTranslation($langcode, $values[$langcode]); - $translation->setPromoted(TRUE); - } - $node->save(); - - // Test that the frontpage view displays the correct translations. - \Drupal::moduleHandler()->install(array('views'), TRUE); - $this->rebuildContainer(); - $this->doTestTranslations('node', $values); - - // Test that the node page displays the correct translations. - $this->doTestTranslations('node/' . $node->id(), $values); - } - - /** - * Tests that the given path dsiplays the correct translation values. - * - * @param string $path - * The path to be tested. - * @param array $values - * The translation values to be found. - */ - protected function doTestTranslations($path, array $values) { - $languages = language_list(); - foreach ($this->langcodes as $langcode) { - $this->drupalGet($path, array('language' => $languages[$langcode])); - $this->assertText($values[$langcode]['title'], format_string('The %langcode node translation is correctly displayed.', array('%langcode' => $langcode))); - } - } - } diff --git a/core/modules/node/node.tokens.inc b/core/modules/node/node.tokens.inc index 244a52b1b9f84d41b15698b0195ff6d54a7ccc8f..2f5d821be29302efbbe32619b1a39f77373d1ab8 100644 --- a/core/modules/node/node.tokens.inc +++ b/core/modules/node/node.tokens.inc @@ -129,11 +129,11 @@ function node_tokens($type, $tokens, array $data = array(), array $options = arr case 'body': case 'summary': - $translation = \Drupal::entityManager()->getTranslationFromContext($node, $langcode, array('operation' => 'node_tokens')); - if (($items = $translation->get('body')) && !$items->isEmpty()) { + if (($items = $node->getTranslation($langcode)->get('body')) && !$items->isEmpty()) { $item = $items[0]; $instance = field_info_instance('node', 'body', $node->getType()); - $field_langcode = $translation->language()->id; + $field_langcode = field_language($node, 'body', $langcode); + // If the summary was requested and is not empty, use it. if ($name == 'summary' && !empty($item->summary)) { $output = $sanitize ? $item->summary_processed : $item->summary; diff --git a/core/modules/system/language.api.php b/core/modules/system/language.api.php index de43f83bfb626df453cafe402ad375f619b8696f..cfa42ec5b96e923bd8b2797ca639585df791e493 100644 --- a/core/modules/system/language.api.php +++ b/core/modules/system/language.api.php @@ -151,6 +151,17 @@ function hook_language_negotiation_info_alter(array &$negotiation_info) { } } +/** + * Perform alterations on the language fallback candidates. + * + * @param $fallback_candidates + * An array of language codes whose order will determine the language fallback + * order. + */ +function hook_language_fallback_candidates_alter(array &$fallback_candidates) { + $fallback_candidates = array_reverse($fallback_candidates); +} + /** * @} End of "addtogroup hooks". */ diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityManagerTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityManagerTest.php index 7cbe7b5493fdf5740b9a399b00da28f70a894723..bd6d098c491957a35e1577cb7856e8e53cd9df49 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityManagerTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityManagerTest.php @@ -34,7 +34,7 @@ public function testMethods() { $this->assertFalse($entity_manager->hasController('non_existent', 'non_existent'), 'A non existent entity type has no controller.'); $this->assertFalse($entity_manager->hasController('entity_test', 'non_existent'), 'An existent entity type does not have a non existent controller.'); - $this->assertFalse($entity_manager->hasController('entity_test_mulrev', 'view_builder'), 'The test entity does not have specified the view builder.'); + $this->assertFalse($entity_manager->hasController('entity_test_mul', 'view_builder'), 'The test entity does not have specified the view builder.'); $this->assertTrue($entity_manager->hasController('entity_test', 'storage'), 'The test entity has specified the controller class'); } 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 bc0e1952d958643919e87521c60979e33dda77c1..e2b0d4f395a54c2d4c30921e038df39fee098cd2 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityTranslationTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityTranslationTest.php @@ -10,7 +10,6 @@ use Drupal\Core\Language\Language; use Drupal\Core\TypedData\TranslatableInterface; use Drupal\entity_test\Entity\EntityTestMulRev; -use Drupal\Component\Utility\MapArray; /** * Tests entity translation. @@ -84,7 +83,6 @@ function setUp() { $language = new Language(array( 'id' => 'l' . $i, 'name' => $this->randomString(), - 'weight' => $i, )); $this->langcodes[$i] = $language->id; language_save($language); @@ -495,69 +493,6 @@ function testEntityTranslationAPI() { $this->assertEqual($field->getLangcode(), $langcode2, 'Field object has the expected langcode.'); } - /** - * Tests language fallback applied to field and entity translations. - */ - function testLanguageFallback() { - $current_langcode = $this->container->get('language_manager')->getLanguage(Language::TYPE_CONTENT)->id; - $this->langcodes[] = $current_langcode; - - $values = array(); - foreach ($this->langcodes as $langcode) { - $values[$langcode]['name'] = $this->randomName(); - $values[$langcode]['user_id'] = mt_rand(0, 127); - } - - $default_langcode = $this->langcodes[0]; - $langcode = $this->langcodes[1]; - $langcode2 = $this->langcodes[2]; - - $entity_type = 'entity_test_mul'; - $controller = $this->entityManager->getStorageController($entity_type); - $entity = $controller->create(array('langcode' => $default_langcode) + $values[$default_langcode]); - $entity->save(); - - $entity->addTranslation($langcode, $values[$langcode]); - $entity->save(); - - // Check that retrieveing the current translation works as expected. - $entity = $this->reloadEntity($entity); - $translation = $this->entityManager->getTranslationFromContext($entity, $langcode2); - $this->assertEqual($translation->language()->id, $default_langcode, 'The current translation language matches the expected one.'); - - // Check that language fallback respects language weight by default. - $languages = language_list(); - $languages[$langcode]->weight = -1; - language_save($languages[$langcode]); - $translation = $this->entityManager->getTranslationFromContext($entity, $langcode2); - $this->assertEqual($translation->language()->id, $langcode, 'The current translation language matches the expected one.'); - - // Check that the current translation is properly returned. - $translation = $this->entityManager->getTranslationFromContext($entity); - $this->assertEqual($langcode, $translation->language()->id, 'The current translation language matches the topmost language fallback candidate.'); - $entity->addTranslation($current_langcode, $values[$current_langcode]); - $translation = $this->entityManager->getTranslationFromContext($entity); - $this->assertEqual($current_langcode, $translation->language()->id, 'The current translation language matches the current language.'); - - // Check that if the entity has no translation no fallback is applied. - $entity2 = $controller->create(array('langcode' => $default_langcode)); - $translation = $this->entityManager->getTranslationFromContext($entity2, $default_langcode); - $this->assertIdentical($entity2, $translation, 'When the entity has no translation no fallback is applied.'); - - // Checks that entity translations are rendered properly. - $controller = $this->entityManager->getViewBuilder($entity_type); - $build = $controller->view($entity); - $this->assertEqual($build['label']['#markup'], $values[$current_langcode]['name'], 'By default the entity is rendered in the current language.'); - $langcodes = MapArray::copyValuesToKeys($this->langcodes); - // We have no translation for the $langcode2 langauge, hence the expected - // result is the topmost existing translation, that is $langcode. - $langcodes[$langcode2] = $langcode; - foreach ($langcodes as $desired => $expected) { - $build = $controller->view($entity, 'full', $desired); - $this->assertEqual($build['label']['#markup'], $values[$expected]['name'], 'The entity is rendered in the expected language.'); - } - } - /** * Check that field translatability is handled properly. */ diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTest.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTest.php index 69f1aabbe5364d80dc41f9f452486ab1528a3e80..8695b113898ef45b2943f31cab1447ce2cb86d9e 100644 --- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTest.php +++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTest.php @@ -98,11 +98,8 @@ protected function init() { /** * Overrides Drupal\entity\Entity::label(). */ - public function label($langcode = NULL) { + public function label($langcode = Language::LANGCODE_DEFAULT) { $info = $this->entityInfo(); - if (!isset($langcode)) { - $langcode = $this->activeLangcode; - } if (isset($info['entity_keys']['label']) && $info['entity_keys']['label'] == 'name') { return $this->getTranslation($langcode)->name->value; } diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestMul.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestMul.php index 790732283c417b262dc48e6aa37b9f3cd9b6d94e..d3e0fb3a1b0a5071ce5679bf6147bec4b49d5162 100644 --- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestMul.php +++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestMul.php @@ -19,7 +19,6 @@ * label = @Translation("Test entity - data table"), * controllers = { * "storage" = "Drupal\entity_test\EntityTestStorageController", - * "view_builder" = "Drupal\entity_test\EntityTestViewBuilder", * "access" = "Drupal\entity_test\EntityTestAccessController", * "form" = { * "default" = "Drupal\entity_test\EntityTestFormController" diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Form/TermDeleteForm.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Form/TermDeleteForm.php index cec3c3cc3e15d1642909b76965d4159a6d1d17b4..18d3a6a4654e2ef31e0a9d9261f499bc19bdd0ae 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/Form/TermDeleteForm.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Form/TermDeleteForm.php @@ -8,7 +8,7 @@ namespace Drupal\taxonomy\Form; use Symfony\Component\DependencyInjection\ContainerInterface; -use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\taxonomy\VocabularyStorageControllerInterface; use Drupal\Core\Entity\ContentEntityConfirmFormBase; use Drupal\Core\Cache\Cache; @@ -17,6 +17,32 @@ */ class TermDeleteForm extends ContentEntityConfirmFormBase { + /** + * The taxonomy vocabulary storage controller. + * + * @var \Drupal\taxonomy\VocabularyStorageControllerInterface + */ + protected $vocabularyStorageController; + + /** + * Constructs a new TermDelete object. + * + * @param \Drupal\taxonomy\VocabularyStorageControllerInterface $storage_controller + * The Entity manager. + */ + public function __construct(VocabularyStorageControllerInterface $storage_controller) { + $this->vocabularyStorageController = $storage_controller; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity.manager')->getStorageController('taxonomy_vocabulary') + ); + } + /** * {@inheritdoc} */ @@ -59,8 +85,7 @@ public function getConfirmText() { */ public function submit(array $form, array &$form_state) { $this->entity->delete(); - $storage_controller = $this->entityManager->getStorageController('taxonomy_vocabulary'); - $vocabulary = $storage_controller->load($this->entity->bundle()); + $vocabulary = $this->vocabularyStorageController->load($this->entity->bundle()); // @todo Move to storage controller http://drupal.org/node/1988712 taxonomy_check_vocabulary_hierarchy($vocabulary, array('tid' => $this->entity->id())); diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/TermFormController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/TermFormController.php index 0dbaf6322e39df3a36c4a212e6ed8ad3ac7028b3..8821b091990af5005527658b9152081c80d9065a 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/TermFormController.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/TermFormController.php @@ -10,7 +10,6 @@ use Drupal\Core\Cache\Cache; use Drupal\Core\Config\ConfigFactory; use Drupal\Core\Entity\ContentEntityFormController; -use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Language\Language; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -19,6 +18,13 @@ */ class TermFormController extends ContentEntityFormController { + /** + * The vocabulary storage. + * + * @var \Drupal\taxonomy\VocabularyStorageControllerInterface + */ + protected $vocabStorage; + /** * The config factory. * @@ -29,13 +35,13 @@ class TermFormController extends ContentEntityFormController { /** * Constructs a new TermFormController. * - * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager - * The entity manager. + * @param \Drupal\taxonomy\VocabularyStorageControllerInterface $vocab_storage + * The vocabulary storage. * @param \Drupal\Core\Config\ConfigFactory $config_factory * The config factory. */ - public function __construct(EntityManagerInterface $entity_manager, ConfigFactory $config_factory) { - parent::__construct($entity_manager); + public function __construct(VocabularyStorageControllerInterface $vocab_storage, ConfigFactory $config_factory) { + $this->vocabStorage = $vocab_storage; $this->configFactory = $config_factory; } @@ -44,7 +50,7 @@ public function __construct(EntityManagerInterface $entity_manager, ConfigFactor */ public static function create(ContainerInterface $container) { return new static( - $container->get('entity.manager'), + $container->get('entity.manager')->getStorageController('taxonomy_vocabulary'), $container->get('config.factory') ); } @@ -54,8 +60,7 @@ public static function create(ContainerInterface $container) { */ public function form(array $form, array &$form_state) { $term = $this->entity; - $vocab_storage = $this->entityManager->getStorageController('taxonomy_vocabulary'); - $vocabulary = $vocab_storage->load($term->bundle()); + $vocabulary = $this->vocabStorage->load($term->bundle()); $parent = array_keys(taxonomy_term_load_parents($term->id())); $form_state['taxonomy']['parent'] = $parent; diff --git a/core/modules/user/lib/Drupal/user/AccountFormController.php b/core/modules/user/lib/Drupal/user/AccountFormController.php index fdb39ae8f5e610315793a3f5a5933b114d7815e3..714011222c02c5863d2a839f7cfba3e64619ea5f 100644 --- a/core/modules/user/lib/Drupal/user/AccountFormController.php +++ b/core/modules/user/lib/Drupal/user/AccountFormController.php @@ -8,7 +8,6 @@ namespace Drupal\user; use Drupal\Core\Entity\ContentEntityFormController; -use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Language\Language; use Drupal\Core\Language\LanguageManager; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -28,13 +27,10 @@ abstract class AccountFormController extends ContentEntityFormController { /** * Constructs a new EntityFormController object. * - * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager - * The entity manager. * @param \Drupal\Core\Language\LanguageManager $language_manager * The language manager. */ - public function __construct(EntityManagerInterface $entity_manager, LanguageManager $language_manager) { - parent::__construct($entity_manager); + public function __construct(LanguageManager $language_manager) { $this->languageManager = $language_manager; } @@ -43,7 +39,6 @@ public function __construct(EntityManagerInterface $entity_manager, LanguageMana */ public static function create(ContainerInterface $container) { return new static( - $container->get('entity.manager'), $container->get('language_manager') ); } diff --git a/core/modules/user/lib/Drupal/user/Form/UserCancelForm.php b/core/modules/user/lib/Drupal/user/Form/UserCancelForm.php index 59d6145f458fec26dc2bf2887beae016c7c31853..973c84e97fdb19329da10fd7aac5aad0acb10d31 100644 --- a/core/modules/user/lib/Drupal/user/Form/UserCancelForm.php +++ b/core/modules/user/lib/Drupal/user/Form/UserCancelForm.php @@ -9,7 +9,6 @@ use Drupal\Core\Config\ConfigFactory; use Drupal\Core\Entity\ContentEntityConfirmFormBase; -use Drupal\Core\Entity\EntityManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -43,11 +42,8 @@ class UserCancelForm extends ContentEntityConfirmFormBase { * * @param \Drupal\Core\Config\ConfigFactory $config_factory * The config factory. - * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager - * The entity manager. */ - public function __construct(EntityManagerInterface $entity_manager, ConfigFactory $config_factory) { - parent::__construct($entity_manager); + public function __construct(ConfigFactory $config_factory) { $this->configFactory = $config_factory; } @@ -56,7 +52,6 @@ public function __construct(EntityManagerInterface $entity_manager, ConfigFactor */ public static function create(ContainerInterface $container) { return new static( - $container->get('entity.manager'), $container->get('config.factory') ); }