diff --git a/core/lib/Drupal/Core/Entity/ContentEntityFormController.php b/core/lib/Drupal/Core/Entity/ContentEntityFormController.php index 568d5cb..86a22aa 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityFormController.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityFormController.php @@ -87,10 +87,15 @@ protected function init(array &$form_state) { */ 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; + // Try to load the target langcode from a query parameter. If this is not + // given, load it via current context. + $content_translation_target = \Drupal::request()->get('content_translation_target'); + if (isset($content_translation_target)) { + $form_state['langcode'] = $content_translation_target; + } + else { + $form_state['langcode'] = $this->entityManager->getTranslationFromContext($this->entity)->language()->id; + } } return $form_state['langcode']; } diff --git a/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module index d7204a5..83cea25 100644 --- a/core/modules/content_translation/content_translation.module +++ b/core/modules/content_translation/content_translation.module @@ -61,6 +61,16 @@ function content_translation_module_implements_alter(&$implementations, $hook) { unset($implementations['content_translation']); $implementations['content_translation'] = $group; break; + + // Since in this hook implementation we are changing the form language, by + // acting first we minimize the risk of having inconsistent behaviors due to + // different hook_entity_prepare_form() implementations assuming different + // form languages. + case 'entity_prepare_form': + $group = $implementations['content_translation']; + unset($implementations['content_translation']); + $implementations = array('content_translation' => $group) + $implementations; + break; } } @@ -240,6 +250,84 @@ function _content_translation_menu_strip_loaders($path) { } /** + * Implements hook_entity_prepare_form(). + */ +function content_translation_entity_prepare_form(EntityInterface $entity, $form_display, $operation, array &$form_state) { + if ($entity instanceof ContentEntityInterface) { + $languages = language_list(); + $source = Drupal::request()->get('content_translation_source'); + $target = Drupal::request()->get('content_translation_target'); + content_translation_prepare_translation_form($entity, isset($languages[$source]) ? $source : FALSE, isset($languages[$target]) ? $target : NULL, $form_state); + } +} + +/** + * Prepares the entity and the form state to display a translation form. + * + * @param ContentEntityInterface $entity + * The entity being translated. + * @param string $source + * The language code to be used as source. + * @param string $target + * The language code to be used as target. + * @param array $form_state + * An associative array representing the state of the form. + */ +function content_translation_prepare_translation_form(ContentEntityInterface $entity, $source, $target, array &$form_state) { + if (empty($form_state['content_translation']['prepared']) && $entity->isTranslatable() && content_translation_access($entity, $source ? 'create' : 'update')) { + // Avoid preparing the entity form twice. + $form_state['content_translation']['prepared'] = TRUE; + + // If no valid translation language is specified, we just let the entity + // form controller determine most appropriate form language based on the + // entity data. + if (isset($target) && ($source || ($translations = $entity->getTranslationLanguages()) && isset($translations[$target]))) { + $form_state['langcode'] = $target; + $form_state['content_translation']['target'] = $target; + } + $form_state['content_translation']['source'] = $source; + + // Translators do not see the full entity form, just the translatable bits. + $form_state['content_translation']['translation_form'] = !$entity->access('update'); + + // If we have a source language defined we are creating a new translation + // for which we need to prepare the initial values. + if ($source) { + content_translation_prepare_translation($entity, $source, $target); + } + } +} + +/** + * Populates target values with the source values. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity being translated. + * @param string $source + * The language code to be used as source. + * @param string $target + * The language code to be used as target. + */ +function content_translation_prepare_translation(EntityInterface $entity, $source, $target) { + // @todo Unify field and property handling. + if ($entity instanceof ContentEntityInterface) { + $source_translation = $entity->getTranslation($source); + $entity->addTranslation($target, $source_translation->getPropertyValues()); + } + else { + $instances = field_info_instances($entity->entityType(), $entity->bundle()); + foreach ($instances as $field_name => $instance) { + $field = field_info_field($entity->entityType(), $field_name); + if (!empty($field['translatable'])) { + $value = $entity->get($field_name); + $value[$target] = isset($value[$source]) ? $value[$source] : array(); + $entity->set($field_name, $value); + } + } + } +} + +/** * Access callback for the translation overview page. * * @param \Drupal\Core\Entity\EntityInterface $entity diff --git a/core/modules/content_translation/content_translation.pages.inc b/core/modules/content_translation/content_translation.pages.inc index 16864bf..c750e5e 100644 --- a/core/modules/content_translation/content_translation.pages.inc +++ b/core/modules/content_translation/content_translation.pages.inc @@ -84,6 +84,7 @@ function content_translation_overview(EntityInterface $entity) { } elseif (!$is_original && $controller->getTranslationAccess($entity, 'update')) { $links['edit'] = isset($translate_links->links[$langcode]['href']) ? $translate_links->links[$langcode] : array('href' => $translate_path, 'language' => $language); + $links['edit']['query']['content_translation_target'] = $langcode; } if (isset($links['edit'])) { @@ -183,14 +184,8 @@ function _content_translation_get_switch_links($path) { * Use \Drupal\content_translation\Controller\ContentTranslationController::add(). */ function content_translation_add_page(EntityInterface $entity, Language $source = NULL, Language $target = NULL) { - $source = !empty($source) ? $source : $entity->language(); - $target = !empty($target) ? $target : \Drupal::languageManager()->getCurrentLanguage(Language::TYPE_CONTENT); - // @todo Exploit the upcoming hook_entity_prepare() when available. - content_translation_prepare_translation($entity, $source, $target); - $form_state['langcode'] = $target->id; - $form_state['content_translation']['source'] = $source; - $form_state['content_translation']['target'] = $target; - $form_state['content_translation']['translation_form'] = !$entity->access('update'); + $form_state = array(); + content_translation_prepare_translation_form($entity, $source->id, $target->id, $form_state); return \Drupal::service('entity.form_builder')->getForm($entity, 'default', $form_state); } @@ -210,25 +205,49 @@ function content_translation_add_page(EntityInterface $entity, Language $source * Use \Drupal\content_translation\Controller\ContentTranslationController::edit(). */ function content_translation_edit_page(EntityInterface $entity, Language $language = NULL) { - $language = !empty($language) ? $language : \Drupal::languageManager()->getCurrentLanguage(Language::TYPE_CONTENT); - $form_state['langcode'] = $language->id; + $form_state = array(); + content_translation_prepare_translation_form($entity, FALSE, $language->id, $form_state); $form_state['content_translation']['translation_form'] = TRUE; return \Drupal::service('entity.form_builder')->getForm($entity, 'default', $form_state); } - /** - * Populates target values with the source values. + * Form constructor for the translation deletion confirmation. * - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entitiy being translated. - * @param \Drupal\Core\Language\Language $source - * The language to be used as source. - * @param \Drupal\Core\Language\Language $target - * The language to be used as target. + * @deprecated Use \Drupal\content_translation\Form\ContentTranslationForm::deleteTranslation() + */ +function content_translation_delete_confirm(array $form, array $form_state, EntityInterface $entity, Language $language) { + $controller = content_translation_controller($entity->entityType()); + $uri = $entity->uri('drupal:content-translation-overview'); + + return confirm_form( + $form, + t('Are you sure you want to delete the @language translation of %label?', array('@language' => $language->name, '%label' => $entity->label())), + $uri['path'], + t('This action cannot be undone.'), + t('Delete'), + t('Cancel') + ); +} + +/** + * Form submission handler for content_translation_delete_confirm(). */ -function content_translation_prepare_translation(EntityInterface $entity, Language $source, Language $target) { - if ($entity instanceof ContentEntityInterface) { - $source_translation = $entity->getTranslation($source->id); - $entity->addTranslation($target->id, $source_translation->toArray()); +function content_translation_delete_confirm_submit(array $form, array &$form_state) { + list($entity, $language) = $form_state['build_info']['args']; + $controller = content_translation_controller($entity->entityType()); + + // Remove the translated values. + $entity->removeTranslation($language->id); + $entity->save(); + + // Remove any existing path alias for the removed translation. + // @todo This should be taken care of by the Path module. + if (\Drupal::moduleHandler()->moduleExists('path')) { + $uri = $entity->uri(); + $conditions = array('source' => $uri['path'], 'langcode' => $language->id); + \Drupal::service('path.crud')->delete($conditions); } + + $uri = $entity->uri('drupal:content-translation-overview'); + $form_state['redirect'] = $uri['path']; } diff --git a/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationHandler.php b/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationHandler.php index 76723c2..c2467d4 100644 --- a/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationHandler.php +++ b/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationHandler.php @@ -73,7 +73,7 @@ public function getTranslationAccess(EntityInterface $entity, $op) { * {@inheritdoc} */ public function getSourceLangcode(array $form_state) { - return isset($form_state['content_translation']['source']) ? $form_state['content_translation']['source']->id : FALSE; + return !empty($form_state['content_translation']['source']) ? $form_state['content_translation']['source'] : FALSE; } /** 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 96bf8b2..1e02ee8 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 @@ -35,6 +35,7 @@ */ function testTranslationUI() { $this->doTestBasicTranslation(); + $this->assertFormLanguage(); $this->doTestTranslationOverview(); $this->doTestOutdatedStatus(); $this->doTestPublishedStatus(); @@ -214,6 +215,21 @@ protected function doTestAuthoringInfo() { } /** + * Tests the form language switch functionality. + */ + protected function assertFormLanguage() { + $entity = entity_load($this->entityType, $this->entityId, TRUE); + + $uri = $entity->uri('edit-form'); + $message = 'The form language can be switched to @langcode through a query string parameter'; + foreach ($entity->getTranslationLanguages() as $langcode => $language) { + $options = array('query' => array('content_translation_target' => $langcode)); + $this->drupalGet($uri['path'], $options); + $this->assertRaw($entity->getTranslation($langcode)->{$this->fieldName}->value, format_string($message, array('@langcode' => $langcode))); + } + } + + /** * Tests translation deletion. */ protected function doTestTranslationDeletion() { diff --git a/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationWorkflowsTest.php b/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationWorkflowsTest.php index 43e5d77..3070019 100644 --- a/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationWorkflowsTest.php +++ b/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationWorkflowsTest.php @@ -125,7 +125,11 @@ protected function assertWorkflows(UserInterface $user, $expected_status) { // Check whether the user is allowed to access the entity form in edit mode. $edit_path = $this->entity->getSystemPath('edit-form'); - $options = array('language' => $languages[$default_langcode]); + $options = array( + 'language' => $languages[$default_langcode], + 'query' => array('content_translation_target' => $default_langcode), + ); + $this->drupalGet($edit_path, $options); $this->assertResponse($expected_status['edit'], format_string('The @user_label has the expected edit access.', $args)); @@ -163,7 +167,8 @@ protected function assertWorkflows(UserInterface $user, $expected_status) { if ($editor) { $this->clickLink('Edit', 2); // An editor should be pointed to the entity form in multilingual mode. - $this->assertUrl($edit_path, $options, 'The translation overview points to the edit form for editors when editing translations.'); + $query = array('query' => array('content_translation_target' => $langcode)); + $this->assertUrl($edit_path, $options + $query, 'The translation overview points to the edit form for editors when editing translations.'); } else { $this->clickLink('Edit');