Problem/Motivation

It is currently impossible to translate a content entity using the Content Translation module that does not provide a canonical link template.

That is because content_translation_page_attachments(), which puts some links in the HTML head for accessibility, does not check for a link template before generating a URL to it.

Proposed resolution

Add a check for $entity->hasLinkTemplate('canonical').

Remaining tasks

User interface changes

API changes

CommentFileSizeAuthor
#30 content-translation-canonical-2479377-30-interdiff.txt4.59 KBdpi
#30 content-translation-canonical-2479377-30.patch7.26 KBdpi
#25 content-translation-canonical-2479377-25.patch7.4 KBdpi
#25 content-translation-canonical-2479377-25-interdiff.txt4.31 KBdpi
#25 content-translation-canonical-2479377-25-testonly.patch6.23 KBdpi
#1 2479377-1-content-translation-page-attachments.patch904 byteststoeckler
#4 2479377-1-content-translation-page-attachments.patch904 bytesmgifford
#6 interdiff-2479377-4-6.txt787 bytesedurenye
#6 2479377-6.patch891 bytesedurenye
#8 2479377-8.patch891 bytesandypost
#17 content-translation-canonical-2479377-17-interdiff.txt7.33 KBdpi
#17 content-translation-canonical-2479377-17.patch7.45 KBdpi
#18 content-translation-canonical-2479377-17-testonly.patch6.56 KBdpi
#23 content-translation-canonical-2479377-23.patch7.47 KBdpi
#23 content-translation-canonical-2479377-23-interdiff.txt820 bytesdpi
#23 content-translation-canonical-2479377-23-testonly.patch6.58 KBdpi
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

tstoeckler’s picture

Status: Active » Needs review
FileSize
904 bytes

Here we go. I added canonical explicitly as an argument to urlInfo() (even though it's optional), to make it clear for what this check is needed.

tstoeckler’s picture

As adding a test for this would require a completely new entity type, of which we already have ~ a dozen, I hope that's necessary. It's totally doable, it would just be more effort (both in terms of actually writing the code but also in terms of maintenance) than the test coverage is worth, in my opinion.

tstoeckler’s picture

This is related to #2449457: inconsistent checks in content_translation. People using the content_translation_ui_skip will hit this problem when providing e.g. an edit form for their entity (if they don't add a canonical link template).

mgifford’s picture

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

edurenye’s picture

+++ b/core/modules/content_translation/content_translation.module
@@ -523,10 +523,10 @@ function content_translation_page_attachments(&$page) {
       foreach ($entity->getTranslationLanguages() as $language) {
-        $url = $entity->urlInfo()
+        $url = $entity->urlInfo('canonical')
           ->setOption('language', $language)

'canonical' is the default value, no need to add it, also I suggest to change it to toUrl() as urlInfo() is deprecated.

So I'm providing a patch with this done.

This error breaks some contrib modules like courier and all based on it.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

andypost’s picture

mglaman’s picture

This is affecting Drupal Commerce where entities do not have canonical routes #2826693: Content translation breaks the promotion forms.

hitfactory’s picture

Also affects Entityqueue which recently removed its canonical link.
Subqueues should not have a canonical link in order for them to be exportable through REST

Confirming patch in #8 prevents an error from being thrown.

jespermb’s picture

I can confirm that the issue with EntityQueues is fixed with the patch in #8.

amateescu’s picture

Status: Needs review » Needs work
+++ b/core/modules/content_translation/content_translation.module
@@ -572,10 +572,10 @@ function content_translation_page_attachments(&$page) {
+        $url = $entity->toUrl()

I think it's better to pass 'canonical' as an explicit parameter like @tstoeckler did in the initial patch, it makes it easier to understand the hasLinkTemplate() check above.

Rajab Natshah’s picture

Testing ....

lluvigne’s picture

Status: Needs work » Needs review

I confirm that patch in #8 fixed the problems here. I dont know if it's better or not to pass 'canonical' as parameter since toUrl() method have 'canonical' as default value and seems like its redundant. So, the patch in #8 looks fine to me.

Rajab Natshah’s picture

Patch in #8 fixed the issue.

meramo’s picture

I confirm. The issue is fixed now with the latest patch

dpi’s picture

Added test coverage for link tags.

Restored the explicit rel which was removed in #6, I agree with #1 it should remain. Other than that this new patch is exclusively new tests:

  • Ensure no canonical links are present for entity type without canonical link. Found in \Drupal\Tests\content_translation\Functional\ContentTranslationLinkTagTest::testCanonicalAlternateTagsMissing.
  • The only existing coverage for the link tags is found in the node-specific \Drupal\node\Tests\NodeTranslationUITest::doTestAlternateHreflangLinks. The new test found in \Drupal\Tests\content_translation\Functional\ContentTranslationLinkTagTest::testCanonicalAlternateTags uses the generic entity test entities.
dpi’s picture

The last submitted patch, 17: content-translation-canonical-2479377-17.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 18: content-translation-canonical-2479377-17-testonly.patch, failed testing.

andypost’s picture

Version: 8.2.x-dev » 8.4.x-dev
Status: Needs work » Needs review

queued retests, I think patch should go into 8.4 first and then backported to 8.3 & 8.2

Status: Needs review » Needs work

The last submitted patch, 18: content-translation-canonical-2479377-17-testonly.patch, failed testing.

dpi’s picture

Fixed PHP syntax issue. Apparently I was using a PHP 7 only feature without knowing it.

Interdiff with #17

tstoeckler’s picture

Status: Needs review » Needs work

The code itself looks great. The test is also very awesome, very very thorough and you get extra credit for not using nodes. I think this is really close. I just have a couple minor notes for the test. Most of them are pretty minor, though, so feel free to ignore them, just sending back to "needs work" once, I promise I'll RTBC the next one ;-)

  1. +++ b/core/modules/content_translation/tests/src/Functional/ContentTranslationLinkTagTest.php
    @@ -0,0 +1,154 @@
    +   * @var array
    

    Should be string[]

  2. +++ b/core/modules/content_translation/tests/src/Functional/ContentTranslationLinkTagTest.php
    @@ -0,0 +1,154 @@
    +    $this->setupLanguages();
    +    // Rebuild the container so that the new languages are picked up by services
    +    // that hold a list of languages.
    +    $this->rebuildContainer();
    

    Super minor but would it make sense conceptually to move this into setupLanguages()?

  3. +++ b/core/modules/content_translation/tests/src/Functional/ContentTranslationLinkTagTest.php
    @@ -0,0 +1,154 @@
    +    $this->langcodes[] = $this->languageManager
    +      ->getDefaultLanguage()
    +      ->getId();
    ...
    +    $default_langcode = $this->languageManager->getDefaultLanguage()->getId();
    +    $non_default_langcodes = array_diff($this->langcodes, [$default_langcode]);
    

    This seems a bit unnecessarily complex to first add the default langcode and then remove it for that again when using it here. It's only used in other place so maybe we could just add the default there?

    That would also to statically define the list of langcodes with the variable declaration, which is nice IMO.

  4. +++ b/core/modules/content_translation/tests/src/Functional/ContentTranslationLinkTagTest.php
    @@ -0,0 +1,154 @@
    +    $this->assertTrue($definition->hasLinkTemplate('canonical'), 'Canonical link template found for entity_test.');
    ...
    +    // Ensure 'canonical' link template does not exist, in case it is added in
    +    // the future.
    +    $this->assertFalse($definition->hasLinkTemplate('canonical'), 'Canonical link template does not exist for entity_test_translatable_no_skip entity.');
    

    Yay, this is awesome. This is exactly how tests should be written IMO: making sure that the conditions for them making sense are met. Thank you!!!

  5. +++ b/core/modules/content_translation/tests/src/Functional/ContentTranslationLinkTagTest.php
    @@ -0,0 +1,154 @@
    +    $urls = array_map(
    +      function ($langcode) use ($url_base) {
    +        $url = clone $url_base;
    +        return $url
    +          ->setOption('language', $this->languageManager->getLanguage($langcode))
    +          ->setAbsolute();
    +      }, $this->langcodes
    +    );
    +
    +    // Ensure link tags are found in languages.
    +    foreach ($urls as $url) {
    +      $langcode = $url->getOption('language')->getId();
    

    Again kind of unnecessary IMO to first iterate over the langcodes and set the languag on the URL and then fetch the langcode again a couple lines below.

    We could also just iterate over the langcode directly and then clone and set the language in that foreach. (Seems the setAbsolute() could be moved to $url_base, right?)

dpi’s picture

The test only patch in #23 should not have passed. During cleanup I removed the a permission, which caused the assert-missing to erroneously pass on an access denied page. Fixed in this patch by adding correct permission, and checking HTTP response code.

Thanks for the review @tstoeckler!

array to string[]

Done

Super minor but would it make sense conceptually to move this into setupLanguages()?

Done

This seems a bit unnecessarily complex to first add the default langcode and then remove it for that again when using it here.

Considering the variable name could be considered to be all languages on the site I thought it made sense. Taken feedback into account, changed it to be added languages only.

kind of unnecessary IMO to first iterate over the langcodes and set the languag on the URL and then fetch the langcode again a couple lines below.

I considered adding keys to $urls. The langcodes were already in the objects, keeping it functional.

Made the change anyhow.

This is exactly how tests should be written

Thanks!

Seems the setAbsolute() could be moved to $url_base, right?)

Done

We could also just iterate over the langcode directly and then clone and set the language in that foreach.

Keep in mind I am iterating over $urls twice, to get a multidimensional test. (Nested foreach ($urls as $url) {), so I need to set the URLs beforehand. Otherwise it gets messy having to create URLs 9 times, instead of 3. ...I could have misinterpreted this comment.

Interdiff is vs #23

This test only patch should fail.

tstoeckler’s picture

Status: Needs review » Reviewed & tested by the community

Awesome, looks absolutely beautiful now. Thank you a lot and for dealing with my annoying nitpicks.

+++ b/core/modules/content_translation/tests/src/Functional/ContentTranslationLinkTagTest.php
@@ -147,6 +146,7 @@ public function testCanonicalAlternateTagsMissing() {
+    $this->assertSession()->statusCodeEquals(200);

Nice catch! Absolutely makes sense.

+++ b/core/modules/content_translation/tests/src/Functional/ContentTranslationLinkTagTest.php
@@ -0,0 +1,154 @@
+    $urls = array_map(
+      function ($langcode) use ($url_base) {
...
+      array_combine($langcodes_all, $langcodes_all)
+    );

That's pretty smart. Thanks for explaining that in IRC. Learned something new today!

The last submitted patch, 25: content-translation-canonical-2479377-25-testonly.patch, failed testing.

dpi’s picture

Great!

Testbot looks good.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/modules/content_translation/tests/src/Functional/ContentTranslationLinkTagTest.php
    @@ -0,0 +1,154 @@
    +  /**
    +   * The language manager.
    +   *
    +   * @var \Drupal\Core\Language\LanguageManagerInterface
    +   */
    +  protected $languageManager;
    +
    +  /**
    +   * The entity type manager.
    +   *
    +   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
    +   */
    +  protected $entityTypeManager;
    ...
    +    $this->languageManager = $this->container->get('language_manager');
    +    $this->entityTypeManager = $this->container->get('entity_type.manager');
    +    $this->setupUsers();
    +    $this->setupLanguages();
    ...
    +    // Rebuild the container so that the new languages are picked up by services
    +    // that hold a list of languages.
    +    $this->rebuildContainer();
    

    Rebuilding the container after attaching services to the test is a bit dangerous - if the rebuild changed these services this wouldn't work. Best thing here is to not attach the services to protected class properties at all.

  2. +++ b/core/modules/content_translation/tests/src/Functional/ContentTranslationLinkTagTest.php
    @@ -0,0 +1,154 @@
    +  protected function setupLanguages() {
    ...
    +  protected function setupUsers() {
    

    Imho these methods should be be inlined into ::setUp()... it's a level of indirection that doesn't help test understandability.

  3. +++ b/core/modules/content_translation/tests/src/Functional/ContentTranslationLinkTagTest.php
    @@ -0,0 +1,154 @@
    +  public function testCanonicalAlternateTags() {
    ...
    +  public function testCanonicalAlternateTagsMissing() {
    

    Great to see complete testing of the problem space.

dpi’s picture

Addressed feedback from #29

these methods should be be inlined into ::setUp()... it's a level of indirection that doesn't help test understandability.

No problem, these were remnants of abstractions originating from its sister web tests extending \Drupal\content_translation\Tests\ContentTranslationTestBase.

Interdiff with #25

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed and pushed cd8293e to 8.4.x and 94389e7 to 8.3.x. Thanks!

As this is a bug fix and makes n o BC implicating changes backporting to 8.3.x immediately.

  • alexpott committed cd8293e on 8.4.x
    Issue #2479377 by dpi, edurenye, tstoeckler, andypost, mgifford,...

  • alexpott committed 94389e7 on 8.3.x
    Issue #2479377 by dpi, edurenye, tstoeckler, andypost, mgifford,...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.