From 17454c5227ff4006144d03a99cf629ef48d70a02 Mon Sep 17 00:00:00 2001 From: upchuk Date: Sun, 21 Jan 2024 17:14:30 +0100 Subject: [PATCH] EWPP-3399: Dashboard links for new local translations. --- .../ActiveRevisionTest.php | 2 + .../TranslationDashboardAlterSubscriber.php | 91 +++++++++++++++++++ .../CorporateWorkflowTranslationTest.php | 74 ++++++++++++++- .../TranslationDashboardAlterSubscriber.php | 58 ++++++++++++ .../src/Functional/LocalTranslationsTest.php | 27 ++++++ 5 files changed, 250 insertions(+), 2 deletions(-) diff --git a/modules/oe_translation_corporate_workflow/modules/oe_translation_active_revision/tests/src/FunctionalJavascript/ActiveRevisionTest.php b/modules/oe_translation_corporate_workflow/modules/oe_translation_active_revision/tests/src/FunctionalJavascript/ActiveRevisionTest.php index 1f702d4b..55356c1e 100644 --- a/modules/oe_translation_corporate_workflow/modules/oe_translation_active_revision/tests/src/FunctionalJavascript/ActiveRevisionTest.php +++ b/modules/oe_translation_corporate_workflow/modules/oe_translation_active_revision/tests/src/FunctionalJavascript/ActiveRevisionTest.php @@ -1148,9 +1148,11 @@ public function testMappingOperationsForMultipleVersions(): void { $french_mapping_operations = $french_row->findAll('xpath', '//td[6]//a'); $this->assertOperationLinks([ 'View' => TRUE, + 'Add new local translation' => TRUE, ], $french_published_operations); $this->assertOperationLinks([ 'View' => TRUE, + 'Add new local translation' => TRUE, // We cannot delete the validated translation if we have a mapping. 'Delete translation' => FALSE, 'Add mapping' => FALSE, diff --git a/modules/oe_translation_corporate_workflow/src/EventSubscriber/TranslationDashboardAlterSubscriber.php b/modules/oe_translation_corporate_workflow/src/EventSubscriber/TranslationDashboardAlterSubscriber.php index 362121fa..4044d66a 100644 --- a/modules/oe_translation_corporate_workflow/src/EventSubscriber/TranslationDashboardAlterSubscriber.php +++ b/modules/oe_translation_corporate_workflow/src/EventSubscriber/TranslationDashboardAlterSubscriber.php @@ -17,10 +17,13 @@ use Drupal\oe_translation\EntityRevisionInfoInterface; use Drupal\oe_translation\Event\ContentTranslationDashboardAlterEvent; use Drupal\oe_translation_corporate_workflow\CorporateWorkflowTranslationTrait; +use Drupal\oe_translation_local\TranslationRequestLocal; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** * Subscribes to the translation dashboard alteration event. + * + * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) */ class TranslationDashboardAlterSubscriber implements EventSubscriberInterface { @@ -210,6 +213,8 @@ protected function alterExistingTranslationsTable(array &$build, ContentEntityIn } $latest_entity_version = $this->getEntityVersion($latest_entity); + $current_entity_started_languages = $this->getStartedLocalTranslationRequestLanguages($entity); + $latest_entity_started_languages = $this->getStartedLocalTranslationRequestLanguages($latest_entity); // Create an array of languages across both versions, with info related // to each. @@ -233,6 +238,34 @@ protected function alterExistingTranslationsTable(array &$build, ContentEntityIn 'operations' => $this->getTranslationOperations($translation, TRUE), ]; + if (!isset($info['operations']['#links'])) { + $info['operations'] = [ + '#type' => 'operations', + '#links' => [], + ]; + } + + // Add the operation to create a new local translation to the current + // entity. + if (!in_array($language->getId(), $current_entity_started_languages) && $language->getId() !== $entity->getUntranslated()->language()->getId()) { + $url = Url::fromRoute('oe_translation_local.create_local_translation_request', [ + 'entity_type' => $entity->getEntityTypeId(), + 'entity' => $entity->getRevisionId(), + 'source' => $entity->getUntranslated()->language()->getId(), + 'target' => $language->getId(), + ], ['query' => ['destination' => Url::fromRoute('')->toString()]]); + + if ($url->access()) { + $link = [ + 'title' => $this->t('Add new local translation'), + 'weight' => -100, + 'url' => $url, + ]; + + $info['operations']['#links']['add'] = $link; + } + } + $languages[$language->getId()][$published_version] = $info; if ($translation && $translation->isDefaultTranslation()) { $languages[$language->getId()]['default_language'] = TRUE; @@ -278,6 +311,36 @@ protected function alterExistingTranslationsTable(array &$build, ContentEntityIn $row['class'][] = 'color-success'; } + // Add the operation to create a new local translation to the latest + // version. + if (!in_array($langcode, $latest_entity_started_languages) && $langcode !== $entity->getUntranslated()->language()->getId()) { + $url = Url::fromRoute('oe_translation_local.create_local_translation_request', [ + 'entity_type' => $entity->getEntityTypeId(), + 'entity' => $latest_entity->getRevisionId(), + 'source' => $latest_entity->getUntranslated()->language()->getId(), + 'target' => $langcode, + ], ['query' => ['destination' => Url::fromRoute('')->toString()]]); + + if ($url->access()) { + $link = [ + 'title' => $this->t('Add new local translation'), + 'weight' => -100, + 'url' => $url, + ]; + + if (is_string($row['data']['operations_validated'])) { + // It means it's empty and set to N/A. + $row['data']['operations_validated'] = [ + 'data' => [ + '#type' => 'operations', + '#links' => [], + ], + ]; + } + $row['data']['operations_validated']['data']['#links']['add'] = $link; + } + } + $rows[] = $row; } @@ -372,4 +435,32 @@ protected function getTranslationOperations(ContentEntityInterface $translation ]; } + /** + * Gets the started local translation requests. + * + * @param \Drupal\Core\Entity\ContentEntityInterface $entity + * The entity. + * + * @return array + * The requests. + */ + protected function getStartedLocalTranslationRequestLanguages(ContentEntityInterface $entity): array { + /** @var \Drupal\oe_translation\TranslationRequestStorageInterface $storage */ + $storage = $this->entityTypeManager->getStorage('oe_translation_request'); + + $languages = []; + + /** @var \Drupal\oe_translation_local\TranslationRequestLocal[] $translation_requests */ + $translation_requests = $storage->getTranslationRequestsForEntityRevision($entity, 'local'); + $translation_requests = array_filter($translation_requests, function (TranslationRequestLocal $translation_request) { + return $translation_request->getTargetLanguageWithStatus()->getStatus() !== TranslationRequestLocal::STATUS_LANGUAGE_SYNCHRONISED; + }); + + foreach ($translation_requests as $request) { + $languages[] = $request->getTargetLanguageWithStatus()->getLangcode(); + } + + return $languages; + } + } diff --git a/modules/oe_translation_corporate_workflow/tests/src/Functional/CorporateWorkflowTranslationTest.php b/modules/oe_translation_corporate_workflow/tests/src/Functional/CorporateWorkflowTranslationTest.php index 6a3e0ae9..33266076 100755 --- a/modules/oe_translation_corporate_workflow/tests/src/Functional/CorporateWorkflowTranslationTest.php +++ b/modules/oe_translation_corporate_workflow/tests/src/Functional/CorporateWorkflowTranslationTest.php @@ -11,6 +11,7 @@ use Drupal\Core\Url; use Drupal\language\Entity\ConfigurableLanguage; use Drupal\node\Entity\Node; +use Drupal\node\NodeInterface; use Drupal\oe_translation\Entity\TranslationRequest; use Drupal\oe_translation\Entity\TranslationRequestInterface; use Drupal\oe_translation_corporate_workflow\CorporateWorkflowTranslationTrait; @@ -35,6 +36,7 @@ * versions. * * @group batch1 + * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) */ class CorporateWorkflowTranslationTest extends BrowserTestBase { @@ -278,6 +280,8 @@ public function testTranslationDashboardExistingTranslations(): void { ], ], ['1.0.0 / published', '2.0.0 / validated']); + $this->assertNewLocalTranslationLinks($node, ['en'], ['en']); + // Create a translation in IT for the published version. $this->clickLink('Local translations'); $this->getSession()->getPage()->find('css', 'tr[hreflang="it"] td[data-version="1.0.0"] a')->click(); @@ -286,8 +290,6 @@ public function testTranslationDashboardExistingTranslations(): void { 'Translation' => 'My node IT', ]; $this->submitForm($values, 'Save and synchronise'); - - // Go back to the dashboard and assert our table got this new language. $this->drupalGet($node->toUrl('drupal:content-translation-overview')); $this->assertExtendedDashboardExistingTranslations([ 'en' => [ @@ -305,6 +307,34 @@ public function testTranslationDashboardExistingTranslations(): void { 'validated_title' => 'No translation', ], ], ['1.0.0 / published', '2.0.0 / validated']); + + // Start another translation and save as draft. + $this->clickLink('Local translations'); + $this->getSession()->getPage()->find('css', 'tr[hreflang="it"] td[data-version="1.0.0"] a')->click(); + $this->assertSession()->elementTextEquals('css', 'h1', 'Translate My node in Italian (version 1.0.0)'); + $values = [ + 'Translation' => 'My node IT', + ]; + $this->submitForm($values, 'Save as draft'); + // Go back to the dashboard and assert the "add local translation" links + // are reflecting this change. + $this->drupalGet($node->toUrl('drupal:content-translation-overview')); + $this->assertNewLocalTranslationLinks($node, ['en', 'it'], ['en']); + // Sync the translation and create one for the validated version. + $this->clickLink('Edit draft translation'); + $this->submitForm($values, 'Save and synchronise'); + $this->assertNewLocalTranslationLinks($node, ['en'], ['en']); + $this->clickLink('Local translations'); + $this->getSession()->getPage()->find('css', 'tr[hreflang="it"] td[data-version="2.0.0"] a')->click(); + $this->assertSession()->elementTextEquals('css', 'h1', 'Translate My node 2 in Italian (version 2.0.0)'); + $values = [ + 'Translation' => 'My node IT 2', + ]; + $this->submitForm($values, 'Save as draft'); + $this->assertNewLocalTranslationLinks($node, ['en'], ['en', 'it']); + // Synchronise it. + $this->clickLink('Edit draft translation'); + $this->submitForm($values, 'Save and synchronise'); } /** @@ -1210,4 +1240,44 @@ protected function assertLocalOngoingRequests(array $expected): void { } } + /** + * Asserts that on the dashboard, we have an operation to add new local trans. + * + * @param \Drupal\node\NodeInterface $node + * The node. + * @param array $current_excluded_languages + * The languages for which we don't have (current entity). + * @param array $latest_excluded_languages + * The languages for which we don't have (current version). + */ + protected function assertNewLocalTranslationLinks(NodeInterface $node, array $current_excluded_languages = [], array $latest_excluded_languages = []): void { + foreach ($this->getSession()->getPage()->findAll('css', 'table.existing-translations-table tbody tr') as $row) { + $langcode = $row->getAttribute('hreflang'); + $col_current = $row->find('xpath', '//td[3]'); + $col_latest = $row->find('xpath', '//td[5]'); + // Current entity. + $current_entity_link = $col_current->findLink('Add new local translation'); + $latest_version_link = $col_latest->findLink('Add new local translation'); + $continue = FALSE; + if (in_array($langcode, $current_excluded_languages)) { + $this->assertNull($current_entity_link); + $continue = TRUE; + } + if (in_array($langcode, $latest_excluded_languages)) { + $this->assertNull($latest_version_link); + $continue = TRUE; + } + + if ($continue) { + continue; + } + + $node_storage = \Drupal::entityTypeManager()->getStorage('node'); + $current_node = $node_storage->load($node->id()); + $latest_node = $node_storage->loadRevision($node_storage->getLatestRevisionId($node->id())); + $this->assertEquals('/build/admin/oe_translation/translate-local/node/' . $current_node->getRevisionId() . '/en/' . $langcode . '?destination=/build/node/' . $current_node->id() . '/translations', $current_entity_link->getAttribute('href')); + $this->assertEquals('/build/admin/oe_translation/translate-local/node/' . $latest_node->getRevisionId() . '/en/' . $langcode . '?destination=/build/node/' . $latest_node->id() . '/translations', $latest_version_link->getAttribute('href')); + } + } + } diff --git a/modules/oe_translation_local/src/EventSubscriber/TranslationDashboardAlterSubscriber.php b/modules/oe_translation_local/src/EventSubscriber/TranslationDashboardAlterSubscriber.php index 248ba67d..b1ef1247 100644 --- a/modules/oe_translation_local/src/EventSubscriber/TranslationDashboardAlterSubscriber.php +++ b/modules/oe_translation_local/src/EventSubscriber/TranslationDashboardAlterSubscriber.php @@ -5,9 +5,11 @@ namespace Drupal\oe_translation_local\EventSubscriber; use Drupal\Core\Cache\CacheableMetadata; +use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\Core\Url; use Drupal\oe_translation\Event\ContentTranslationDashboardAlterEvent; use Drupal\oe_translation_local\TranslationRequestLocal; use Symfony\Component\EventDispatcher\EventSubscriberInterface; @@ -83,6 +85,8 @@ public function alterDashboard(ContentTranslationDashboardAlterEvent $event) { return $translation_request->getTargetLanguageWithStatus()->getStatus() !== TranslationRequestLocal::STATUS_LANGUAGE_SYNCHRONISED; }); + $this->addLocalTranslationOperation($build, $translation_requests, $current_entity); + $cache->addCacheTags(['oe_translation_request_list']); if (!$translation_requests) { $element[] = [ @@ -131,4 +135,58 @@ public function alterDashboard(ContentTranslationDashboardAlterEvent $event) { $event->setBuild($build); } + /** + * Adds the link to create a new local translation. + * + * @param array $build + * The page build. + * @param array $translation_requests + * The existing translation requests. + * @param \Drupal\Core\Entity\ContentEntityInterface $entity + * The current entity. + */ + protected function addLocalTranslationOperation(array &$build, array $translation_requests, ContentEntityInterface $entity) { + $started_local_requests = []; + foreach ($translation_requests as $request) { + $started_local_requests[] = $request->getTargetLanguageWithStatus()->getLangcode(); + } + foreach ($build['existing_translations']['table']['#rows'] as $index => &$row) { + $langcode = $row['hreflang']; + if (in_array($langcode, $started_local_requests) || $langcode === $entity->getUntranslated()->language()->getId()) { + // If there is already a started translation request, we don't add any + // operation. + continue; + } + + $url = Url::fromRoute('oe_translation_local.create_local_translation_request', [ + 'entity_type' => $entity->getEntityTypeId(), + 'entity' => $entity->getRevisionId(), + 'source' => $entity->getUntranslated()->language()->getId(), + 'target' => $langcode, + ], ['query' => ['destination' => Url::fromRoute('')->toString()]]); + + if (!$url->access()) { + continue; + } + $link = [ + 'title' => $this->t('Add new local translation'), + 'weight' => -100, + 'url' => $url, + ]; + if (isset($row['data']['operations']['data']['#links'])) { + // It means we have already the operations. + $row['data']['operations']['data']['#links']['add'] = $link; + continue; + } + + // Otherwise, start the operations. + $row['data']['operations']['data'] = [ + '#type' => 'operations', + '#links' => [ + 'add' => $link, + ], + ]; + } + } + } diff --git a/modules/oe_translation_local/tests/src/Functional/LocalTranslationsTest.php b/modules/oe_translation_local/tests/src/Functional/LocalTranslationsTest.php index ae70cb91..589aa5dc 100644 --- a/modules/oe_translation_local/tests/src/Functional/LocalTranslationsTest.php +++ b/modules/oe_translation_local/tests/src/Functional/LocalTranslationsTest.php @@ -8,6 +8,7 @@ use Drupal\Core\Url; use Drupal\language\Entity\ConfigurableLanguage; use Drupal\menu_link_content\Entity\MenuLinkContent; +use Drupal\node\NodeInterface; use Drupal\Tests\oe_translation\Functional\TranslationTestBase; use Drupal\user\Entity\Role; @@ -538,12 +539,16 @@ public function testLocalTranslationDashboard(): void { $first_node->save(); $this->drupalGet($first_node->toUrl()); $this->clickLink('Translate'); + $this->assertAddLocalTranslationOperation($first_node, ['en']); $this->clickLink('Local translations'); $this->getSession()->getPage()->find('css', 'table tbody tr[hreflang="fr"] a')->click(); $element = $this->getSession()->getPage()->find('xpath', "//textarea[contains(@name,'[translation]')]"); $element->setValue('First node FR'); $this->getSession()->getPage()->pressButton('Save as draft'); $this->assertSession()->pageTextContains('The translation has been saved.'); + $this->drupalGet($first_node->toUrl()); + $this->clickLink('Translate'); + $this->assertAddLocalTranslationOperation($first_node, ['fr', 'en']); $second_node = $this->createBasicTestNode(); $second_node->set('title', 'Second node'); @@ -756,4 +761,26 @@ protected function assertLocalLanguagesTable(array $with_edit = []): void { } } + /** + * Asserts that on the dashboard, we have an operation to add new local trans. + * + * @param \Drupal\node\NodeInterface $node + * The node. + * @param array $excluded_languages + * The languages for which we don't have. + */ + protected function assertAddLocalTranslationOperation(NodeInterface $node, array $excluded_languages = []): void { + foreach ($this->getSession()->getPage()->findAll('css', 'table.existing-translations-table tbody tr') as $row) { + $langcode = $row->getAttribute('hreflang'); + $col = $row->find('xpath', '//td[3]'); + $link = $col->findLink('Add new local translation'); + if (in_array($langcode, $excluded_languages)) { + $this->assertNull($link); + continue; + } + + $this->assertEquals('/build/en/admin/oe_translation/translate-local/node/' . $node->getRevisionId() . '/en/' . $langcode . '?destination=/build/en/node/' . $node->id() . '/translations', $link->getAttribute('href')); + } + } + }