1<?php 2 3namespace Drupal\Tests\path\Functional; 4 5/** 6 * Confirm that paths work with translated nodes. 7 * 8 * @group path 9 */ 10class PathLanguageTest extends PathTestBase { 11 12 /** 13 * Modules to enable. 14 * 15 * @var array 16 */ 17 protected static $modules = [ 18 'path', 19 'locale', 20 'locale_test', 21 'content_translation', 22 ]; 23 24 /** 25 * {@inheritdoc} 26 */ 27 protected $defaultTheme = 'stark'; 28 29 /** 30 * A user with permissions to administer content types. 31 * 32 * @var \Drupal\user\UserInterface 33 */ 34 protected $webUser; 35 36 protected function setUp(): void { 37 parent::setUp(); 38 39 $permissions = [ 40 'access administration pages', 41 'administer content translation', 42 'administer content types', 43 'administer languages', 44 'administer url aliases', 45 'create content translations', 46 'create page content', 47 'create url aliases', 48 'edit any page content', 49 'translate any entity', 50 ]; 51 // Create and log in user. 52 $this->webUser = $this->drupalCreateUser($permissions); 53 $this->drupalLogin($this->webUser); 54 55 // Enable French language. 56 $edit = []; 57 $edit['predefined_langcode'] = 'fr'; 58 59 $this->drupalGet('admin/config/regional/language/add'); 60 $this->submitForm($edit, 'Add language'); 61 62 // Enable URL language detection and selection. 63 $edit = ['language_interface[enabled][language-url]' => 1]; 64 $this->drupalGet('admin/config/regional/language/detection'); 65 $this->submitForm($edit, 'Save settings'); 66 67 // Enable translation for page node. 68 $edit = [ 69 'entity_types[node]' => 1, 70 'settings[node][page][translatable]' => 1, 71 'settings[node][page][fields][path]' => 1, 72 'settings[node][page][fields][body]' => 1, 73 'settings[node][page][settings][language][language_alterable]' => 1, 74 ]; 75 $this->drupalGet('admin/config/regional/content-language'); 76 $this->submitForm($edit, 'Save configuration'); 77 78 $definitions = \Drupal::service('entity_field.manager')->getFieldDefinitions('node', 'page'); 79 $this->assertTrue($definitions['path']->isTranslatable(), 'Node path is translatable.'); 80 $this->assertTrue($definitions['body']->isTranslatable(), 'Node body is translatable.'); 81 } 82 83 /** 84 * Tests alias functionality through the admin interfaces. 85 */ 86 public function testAliasTranslation() { 87 $node_storage = $this->container->get('entity_type.manager')->getStorage('node'); 88 $english_node = $this->drupalCreateNode(['type' => 'page', 'langcode' => 'en']); 89 $english_alias = $this->randomMachineName(); 90 91 // Edit the node to set language and path. 92 $edit = []; 93 $edit['path[0][alias]'] = '/' . $english_alias; 94 $this->drupalGet('node/' . $english_node->id() . '/edit'); 95 $this->submitForm($edit, 'Save'); 96 97 // Confirm that the alias works. 98 $this->drupalGet($english_alias); 99 $this->assertSession()->pageTextContains($english_node->body->value); 100 101 // Translate the node into French. 102 $this->drupalGet('node/' . $english_node->id() . '/translations'); 103 $this->clickLink('Add'); 104 105 $edit = []; 106 $edit['title[0][value]'] = $this->randomMachineName(); 107 $edit['body[0][value]'] = $this->randomMachineName(); 108 $french_alias = $this->randomMachineName(); 109 $edit['path[0][alias]'] = '/' . $french_alias; 110 $this->submitForm($edit, 'Save (this translation)'); 111 112 // Clear the path lookup cache. 113 $this->container->get('path_alias.manager')->cacheClear(); 114 115 // Languages are cached on many levels, and we need to clear those caches. 116 $this->container->get('language_manager')->reset(); 117 $this->rebuildContainer(); 118 $languages = $this->container->get('language_manager')->getLanguages(); 119 120 // Ensure the node was created. 121 $node_storage->resetCache([$english_node->id()]); 122 $english_node = $node_storage->load($english_node->id()); 123 $english_node_french_translation = $english_node->getTranslation('fr'); 124 $this->assertTrue($english_node->hasTranslation('fr'), 'Node found in database.'); 125 126 // Confirm that the alias works. 127 $this->drupalGet('fr' . $edit['path[0][alias]']); 128 $this->assertSession()->pageTextContains($english_node_french_translation->body->value); 129 130 // Confirm that the alias is returned for the URL. Languages are cached on 131 // many levels, and we need to clear those caches. 132 $this->container->get('language_manager')->reset(); 133 $languages = $this->container->get('language_manager')->getLanguages(); 134 $url = $english_node_french_translation->toUrl('canonical', ['language' => $languages['fr']])->toString(); 135 136 $this->assertStringContainsString($edit['path[0][alias]'], $url, 'URL contains the path alias.'); 137 138 // Confirm that the alias works even when changing language negotiation 139 // options. Enable User language detection and selection over URL one. 140 $edit = [ 141 'language_interface[enabled][language-user]' => 1, 142 'language_interface[weight][language-user]' => -9, 143 'language_interface[enabled][language-url]' => 1, 144 'language_interface[weight][language-url]' => -8, 145 ]; 146 $this->drupalGet('admin/config/regional/language/detection'); 147 $this->submitForm($edit, 'Save settings'); 148 149 // Change user language preference. 150 $edit = ['preferred_langcode' => 'fr']; 151 $this->drupalGet("user/" . $this->webUser->id() . "/edit"); 152 $this->submitForm($edit, 'Save'); 153 154 // Check that the English alias works. In this situation French is the 155 // current UI and content language, while URL language is English (since we 156 // do not have a path prefix we fall back to the site's default language). 157 // We need to ensure that the user language preference is not taken into 158 // account while determining the path alias language, because if this 159 // happens we have no way to check that the path alias is valid: there is no 160 // path alias for French matching the english alias. So the alias manager 161 // needs to use the URL language to check whether the alias is valid. 162 $this->drupalGet($english_alias); 163 $this->assertSession()->pageTextContains($english_node_french_translation->body->value); 164 165 // Check that the French alias works. 166 $this->drupalGet("fr/$french_alias"); 167 $this->assertSession()->pageTextContains($english_node_french_translation->body->value); 168 169 // Disable URL language negotiation. 170 $edit = ['language_interface[enabled][language-url]' => FALSE]; 171 $this->drupalGet('admin/config/regional/language/detection'); 172 $this->submitForm($edit, 'Save settings'); 173 174 // Check that the English alias still works. 175 $this->drupalGet($english_alias); 176 $this->assertSession()->pageTextContains($english_node_french_translation->body->value); 177 178 // Check that the French alias is not available. We check the unprefixed 179 // alias because we disabled URL language negotiation above. In this 180 // situation only aliases in the default language and language neutral ones 181 // should keep working. 182 $this->drupalGet($french_alias); 183 $this->assertSession()->statusCodeEquals(404); 184 185 // The alias manager has an internal path lookup cache. Check to see that 186 // it has the appropriate contents at this point. 187 $this->container->get('path_alias.manager')->cacheClear(); 188 $french_node_path = $this->container->get('path_alias.manager')->getPathByAlias('/' . $french_alias, 'fr'); 189 $this->assertEquals('/node/' . $english_node_french_translation->id(), $french_node_path, 'Normal path works.'); 190 // Second call should return the same path. 191 $french_node_path = $this->container->get('path_alias.manager')->getPathByAlias('/' . $french_alias, 'fr'); 192 $this->assertEquals('/node/' . $english_node_french_translation->id(), $french_node_path, 'Normal path is the same.'); 193 194 // Confirm that the alias works. 195 $french_node_alias = $this->container->get('path_alias.manager')->getAliasByPath('/node/' . $english_node_french_translation->id(), 'fr'); 196 $this->assertEquals('/' . $french_alias, $french_node_alias, 'Alias works.'); 197 // Second call should return the same alias. 198 $french_node_alias = $this->container->get('path_alias.manager')->getAliasByPath('/node/' . $english_node_french_translation->id(), 'fr'); 199 $this->assertEquals('/' . $french_alias, $french_node_alias, 'Alias is the same.'); 200 201 // Confirm that the alias is removed if the translation is deleted. 202 $english_node->removeTranslation('fr'); 203 $english_node->save(); 204 $this->assertPathAliasNotExists('/' . $french_alias, 'fr', NULL, 'Alias for French translation is removed when translation is deleted.'); 205 206 // Check that the English alias still works. 207 $this->drupalGet($english_alias); 208 $this->assertPathAliasExists('/' . $english_alias, 'en', NULL, 'English alias is not deleted when French translation is removed.'); 209 $this->assertSession()->pageTextContains($english_node->body->value); 210 } 211 212} 213