Newer
Older
Angie Byron
committed
<?php
namespace Drupal\Tests\node\Functional;
Angie Byron
committed
Angie Byron
committed
use Drupal\Core\Entity\EntityInterface;
use Drupal\Tests\content_translation\Functional\ContentTranslationUITestBase;
Alex Pott
committed
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Url;
catch
committed
use Drupal\node\Entity\Node;
catch
committed
use Drupal\language\Entity\ConfigurableLanguage;
Angie Byron
committed
/**
* Tests the Node Translation UI.
*
* @group node
Angie Byron
committed
*/
catch
committed
class NodeTranslationUITest extends ContentTranslationUITestBase {
Angie Byron
committed
/**
* {inheritdoc}
*/
protected $defaultCacheContexts = [
'languages:language_interface',
Alex Bronstein
committed
'session',
'theme',
'route',
'timezone',
'url.path.parent',
catch
committed
'url.query_args:_wrapper_format',
];
Angie Byron
committed
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['block', 'language', 'content_translation', 'node', 'datetime', 'field_ui', 'help'];
Angie Byron
committed
/**
* The profile to install as a basis for testing.
*
* @var string
*/
protected $profile = 'standard';
Alex Pott
committed
protected function setUp() {
Alex Pott
committed
$this->entityTypeId = 'node';
Angie Byron
committed
$this->bundle = 'article';
parent::setUp();
// Ensure the help message is shown even with prefixed paths.
$this->drupalPlaceBlock('help_block', ['region' => 'content']);
Angie Byron
committed
// Display the language selector.
$this->drupalLogin($this->administrator);
$edit = ['language_configuration[language_alterable]' => TRUE];
$this->drupalPostForm('admin/structure/types/manage/article', $edit, t('Save content type'));
$this->drupalLogin($this->translator);
}
catch
committed
/**
* Tests the basic translation UI.
*/
public function testTranslationUI() {
catch
committed
parent::testTranslationUI();
$this->doUninstallTest();
}
catch
committed
/**
* Tests changing the published status on a node without fields.
*/
public function testPublishedStatusNoFields() {
catch
committed
// Test changing the published status of an article without fields.
$this->drupalLogin($this->administrator);
// Delete all fields.
$this->drupalGet('admin/structure/types/manage/article/fields');
$this->drupalPostForm('admin/structure/types/manage/article/fields/node.article.' . $this->fieldName . '/delete', [], t('Delete'));
$this->drupalPostForm('admin/structure/types/manage/article/fields/node.article.field_tags/delete', [], t('Delete'));
$this->drupalPostForm('admin/structure/types/manage/article/fields/node.article.field_image/delete', [], t('Delete'));
catch
committed
// Add a node.
$default_langcode = $this->langcodes[0];
$values[$default_langcode] = ['title' => [['value' => $this->randomMachineName()]]];
$this->entityId = $this->createEntity($values[$default_langcode], $default_langcode);
catch
committed
$storage = $this->container->get('entity_type.manager')
->getStorage($this->entityTypeId);
$storage->resetCache([$this->entityId]);
$entity = $storage->load($this->entityId);
catch
committed
// Add a content translation.
$langcode = 'fr';
$language = ConfigurableLanguage::load($langcode);
$values[$langcode] = ['title' => [['value' => $this->randomMachineName()]]];
catch
committed
$entity_type_id = $entity->getEntityTypeId();
$add_url = Url::fromRoute("entity.$entity_type_id.content_translation_add", [
catch
committed
$entity->getEntityTypeId() => $entity->id(),
'source' => $default_langcode,
'target' => $langcode
], ['language' => $language]);
$edit = $this->getEditValues($values, $langcode);
$edit['status[value]'] = FALSE;
$this->drupalPostForm($add_url, $edit, t('Save (this translation)'));
catch
committed
catch
committed
$storage->resetCache([$this->entityId]);
$entity = $storage->load($this->entityId);
catch
committed
$translation = $entity->getTranslation($langcode);
// Make sure we unpublished the node correctly.
$this->assertFalse($this->manager->getTranslationMetadata($translation)->isPublished(), 'The translation has been correctly unpublished.');
}
/**
* {@inheritdoc}
*/
protected function getTranslatorPermissions() {
return array_merge(parent::getTranslatorPermissions(), ['administer nodes', "edit any $this->bundle content"]);
}
/**
* {@inheritdoc}
*/
protected function getEditorPermissions() {
return ['administer nodes', 'create article content'];
}
/**
* {@inheritdoc}
*/
protected function getAdministratorPermissions() {
return array_merge(parent::getAdministratorPermissions(), ['access administration pages', 'administer content types', 'administer node fields', 'access content overview', 'bypass node access', 'administer languages', 'administer themes', 'view the administration theme']);
}
/**
* {@inheritdoc}
*/
protected function getNewEntityValues($langcode) {
return ['title' => [['value' => $this->randomMachineName()]]] + parent::getNewEntityValues($langcode);
}
/**
* {@inheritdoc}
*/
protected function doTestPublishedStatus() {
catch
committed
$storage = $this->container->get('entity_type.manager')
->getStorage($this->entityTypeId);
$storage->resetCache([$this->entityId]);
$entity = $storage->load($this->entityId);
$languages = $this->container->get('language_manager')->getLanguages();
$statuses = [
TRUE,
FALSE,
];
foreach ($statuses as $index => $value) {
// (Un)publish the node translations and check that the translation
// statuses are (un)published accordingly.
foreach ($this->langcodes as $langcode) {
$options = ['language' => $languages[$langcode]];
$url = $entity->urlInfo('edit-form', $options);
$this->drupalPostForm($url, ['status[value]' => $value], t('Save') . $this->getFormSubmitSuffix($entity, $langcode), $options);
catch
committed
$storage->resetCache([$this->entityId]);
$entity = $storage->load($this->entityId);
foreach ($this->langcodes as $langcode) {
// The node is created as unpublished thus we switch to the published
// status first.
$status = !$index;
$translation = $entity->getTranslation($langcode);
$this->assertEqual($status, $this->manager->getTranslationMetadata($translation)->isPublished(), 'The translation has been correctly unpublished.');
}
}
}
/**
* {@inheritdoc}
*/
protected function doTestAuthoringInfo() {
catch
committed
$storage = $this->container->get('entity_type.manager')
->getStorage($this->entityTypeId);
$storage->resetCache([$this->entityId]);
$entity = $storage->load($this->entityId);
$languages = $this->container->get('language_manager')->getLanguages();
$values = [];
// Post different base field information for each translation.
foreach ($this->langcodes as $langcode) {
$user = $this->drupalCreateUser();
$values[$langcode] = [
Dries Buytaert
committed
'uid' => $user->id(),
'created' => REQUEST_TIME - mt_rand(0, 1000),
'sticky' => (bool) mt_rand(0, 1),
'promote' => (bool) mt_rand(0, 1),
];
$edit = [
'uid[0][target_id]' => $user->getUsername(),
'created[0][value][date]' => format_date($values[$langcode]['created'], 'custom', 'Y-m-d'),
'created[0][value][time]' => format_date($values[$langcode]['created'], 'custom', 'H:i:s'),
'sticky[value]' => $values[$langcode]['sticky'],
'promote[value]' => $values[$langcode]['promote'],
];
$options = ['language' => $languages[$langcode]];
$url = $entity->urlInfo('edit-form', $options);
$this->drupalPostForm($url, $edit, $this->getFormSubmitAction($entity, $langcode), $options);
}
catch
committed
$storage->resetCache([$this->entityId]);
$entity = $storage->load($this->entityId);
foreach ($this->langcodes as $langcode) {
$translation = $entity->getTranslation($langcode);
$metadata = $this->manager->getTranslationMetadata($translation);
$this->assertEqual($metadata->getAuthor()->id(), $values[$langcode]['uid'], 'Translation author correctly stored.');
$this->assertEqual($metadata->getCreatedTime(), $values[$langcode]['created'], 'Translation date correctly stored.');
$this->assertEqual($translation->isSticky(), $values[$langcode]['sticky'], 'Sticky of Translation correctly stored.');
$this->assertEqual($translation->isPromoted(), $values[$langcode]['promote'], 'Promoted of Translation correctly stored.');
Angie Byron
committed
}
/**
* Tests that translation page inherits admin status of edit page.
*/
public function testTranslationLinkTheme() {
$this->drupalLogin($this->administrator);
$article = $this->drupalCreateNode(['type' => 'article', 'langcode' => $this->langcodes[0]]);
// Set up Seven as the admin theme and use it for node editing.
$this->container->get('theme_handler')->install(['seven']);
$edit = [];
$edit['admin_theme'] = 'seven';
$edit['use_admin_theme'] = TRUE;
$this->drupalPostForm('admin/appearance', $edit, t('Save configuration'));
$this->drupalGet('node/' . $article->id() . '/translations');
$this->assertRaw('core/themes/seven/css/base/elements.css', 'Translation uses admin theme if edit is admin.');
// Turn off admin theme for editing, assert inheritance to translations.
$edit['use_admin_theme'] = FALSE;
$this->drupalPostForm('admin/appearance', $edit, t('Save configuration'));
$this->drupalGet('node/' . $article->id() . '/translations');
$this->assertNoRaw('core/themes/seven/css/base/elements.css', 'Translation uses frontend theme if edit is frontend.');
// Assert presence of translation page itself (vs. DisabledBundle below).
$this->assertResponse(200);
}
Angie Byron
committed
/**
Jennifer Hodgdon
committed
* Tests that no metadata is stored for a disabled bundle.
Angie Byron
committed
*/
public function testDisabledBundle() {
// Create a bundle that does not have translation enabled.
Alex Pott
committed
$disabledBundle = $this->randomMachineName();
$this->drupalCreateContentType(['type' => $disabledBundle, 'name' => $disabledBundle]);
Angie Byron
committed
// Create a node for each bundle.
$node = $this->drupalCreateNode([
Alex Pott
committed
'type' => $this->bundle,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
]);
Angie Byron
committed
// Make sure that nothing was inserted into the {content_translation} table.
$rows = db_query('SELECT nid, count(nid) AS count FROM {node_field_data} WHERE type <> :type GROUP BY nid HAVING count(nid) >= 2', [':type' => $this->bundle])->fetchAll();
$this->assertEqual(0, count($rows));
// Ensure the translation tab is not accessible.
$this->drupalGet('node/' . $node->id() . '/translations');
$this->assertResponse(403);
Angie Byron
committed
}
/**
* Tests that translations are rendered properly.
*/
public function testTranslationRendering() {
$default_langcode = $this->langcodes[0];
$values[$default_langcode] = $this->getNewEntityValues($default_langcode);
$this->entityId = $this->createEntity($values[$default_langcode], $default_langcode);
catch
committed
$node = \Drupal::entityManager()->getStorage($this->entityTypeId)->load($this->entityId);
$node->setPromoted(TRUE);
// Create translations.
foreach (array_diff($this->langcodes, [$default_langcode]) as $langcode) {
$values[$langcode] = $this->getNewEntityValues($langcode);
$translation = $node->addTranslation($langcode, $values[$langcode]);
// Publish and promote the translation to frontpage.
$translation->setPromoted(TRUE);
$translation->setPublished(TRUE);
}
$node->save();
// Test that the frontpage view displays the correct translations.
\Drupal::service('module_installer')->install(['views'], TRUE);
$this->rebuildContainer();
$this->doTestTranslations('node', $values);
Angie Byron
committed
// Enable the translation language renderer.
catch
committed
$view = \Drupal::entityManager()->getStorage('view')->load('frontpage');
Angie Byron
committed
$display = &$view->getDisplay('default');
Alex Pott
committed
$display['display_options']['rendering_language'] = '***LANGUAGE_entity_translation***';
Angie Byron
committed
$view->save();
// Need to check from the beginning, including the base_path, in the url
// since the pattern for the default language might be a substring of
// the strings for other languages.
$base_path = base_path();
// Check the frontpage for 'Read more' links to each translation.
// See also assertTaxonomyPage() in NodeAccessBaseTableTest.
$node_href = 'node/' . $node->id();
foreach ($this->langcodes as $langcode) {
$this->drupalGet('node', ['language' => \Drupal::languageManager()->getLanguage($langcode)]);
$num_match_found = 0;
if ($langcode == 'en') {
// Site default language does not have langcode prefix in the URL.
$expected_href = $base_path . $node_href;
}
else {
$expected_href = $base_path . $langcode . '/' . $node_href;
}
$pattern = '|^' . $expected_href . '$|';
foreach ($this->xpath("//a[text()='Read more']") as $link) {
if (preg_match($pattern, $link->getAttribute('href'), $matches) == TRUE) {
$num_match_found++;
}
}
$this->assertTrue($num_match_found == 1, 'There is 1 Read more link, ' . $expected_href . ', for the ' . $langcode . ' translation of a node on the frontpage. (Found ' . $num_match_found . '.)');
}
// Check the frontpage for 'Add new comment' links that include the
// language.
$comment_form_href = 'node/' . $node->id() . '#comment-form';
foreach ($this->langcodes as $langcode) {
$this->drupalGet('node', ['language' => \Drupal::languageManager()->getLanguage($langcode)]);
$num_match_found = 0;
if ($langcode == 'en') {
// Site default language does not have langcode prefix in the URL.
$expected_href = $base_path . $comment_form_href;
}
else {
$expected_href = $base_path . $langcode . '/' . $comment_form_href;
}
$pattern = '|^' . $expected_href . '$|';
foreach ($this->xpath("//a[text()='Add new comment']") as $link) {
if (preg_match($pattern, $link->getAttribute('href'), $matches) == TRUE) {
$num_match_found++;
}
}
$this->assertTrue($num_match_found == 1, 'There is 1 Add new comment link, ' . $expected_href . ', for the ' . $langcode . ' translation of a node on the frontpage. (Found ' . $num_match_found . '.)');
}
// Test that the node page displays the correct translations.
$this->doTestTranslations('node/' . $node->id(), $values);
// Test that the node page has the correct alternate hreflang links.
$this->doTestAlternateHreflangLinks($node->urlInfo());
}
/**
* Tests that the given path displays 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 = $this->container->get('language_manager')->getLanguages();
foreach ($this->langcodes as $langcode) {
$this->drupalGet($path, ['language' => $languages[$langcode]]);
$this->assertText($values[$langcode]['title'][0]['value'], format_string('The %langcode node translation is correctly displayed.', ['%langcode' => $langcode]));
}
}
/**
* Tests that the given path provides the correct alternate hreflang links.
*
* @param \Drupal\Core\Url $url
* The path to be tested.
*/
protected function doTestAlternateHreflangLinks(Url $url) {
$languages = $this->container->get('language_manager')->getLanguages();
$url->setAbsolute();
$urls = [];
foreach ($this->langcodes as $langcode) {
$language_url = clone $url;
$urls[$langcode] = $language_url->setOption('language', $languages[$langcode]);
}
foreach ($this->langcodes as $langcode) {
$this->drupalGet($urls[$langcode]);
foreach ($urls as $alternate_langcode => $language_url) {
// Retrieve desired link elements from the HTML head.
$links = $this->xpath('head/link[@rel = "alternate" and @href = :href and @hreflang = :hreflang]',
[':href' => $language_url->toString(), ':hreflang' => $alternate_langcode]);
$this->assert(isset($links[0]), format_string('The %langcode node translation has the correct alternate hreflang link for %alternate_langcode: %link.', ['%langcode' => $langcode, '%alternate_langcode' => $alternate_langcode, '%link' => $url->toString()]));
}
}
}
/**
* {@inheritdoc}
*/
protected function getFormSubmitSuffix(EntityInterface $entity, $langcode) {
if (!$entity->isNew() && $entity->isTranslatable()) {
$translations = $entity->getTranslationLanguages();
if ((count($translations) > 1 || !isset($translations[$langcode])) && ($field = $entity->getFieldDefinition('status'))) {
return ' ' . ($field->isTranslatable() ? t('(this translation)') : t('(all translations)'));
}
}
return '';
}
catch
committed
/**
* Tests uninstalling content_translation.
*/
protected function doUninstallTest() {
// Delete all the nodes so there is no data.
$nodes = Node::loadMultiple();
foreach ($nodes as $node) {
$node->delete();
}
$language_count = count(\Drupal::configFactory()->listAll('language.content_settings.'));
\Drupal::service('module_installer')->uninstall(['content_translation']);
$this->rebuildContainer();
$this->assertEqual($language_count, count(\Drupal::configFactory()->listAll('language.content_settings.')), 'Languages have been fixed rather than deleted during content_translation uninstall.');
}
/**
* {@inheritdoc}
*/
protected function doTestTranslationEdit() {
catch
committed
$storage = $this->container->get('entity_type.manager')
->getStorage($this->entityTypeId);
$storage->resetCache([$this->entityId]);
$entity = $storage->load($this->entityId);
$languages = $this->container->get('language_manager')->getLanguages();
$type_name = node_get_type_label($entity);
foreach ($this->langcodes as $langcode) {
// We only want to test the title for non-english translations.
if ($langcode != 'en') {
$options = ['language' => $languages[$langcode]];
$url = $entity->urlInfo('edit-form', $options);
$this->drupalGet($url);
$title = t('<em>Edit @type</em> @title [%language translation]', [
'@type' => $type_name,
'@title' => $entity->getTranslation($langcode)->label(),
'%language' => $languages[$langcode]->getName(),
]);
$this->assertRaw($title);
}
}
}
Alex Pott
committed
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
/**
* Tests that revision translations are rendered properly.
*/
public function testRevisionTranslationRendering() {
$storage = \Drupal::entityTypeManager()->getStorage('node');
// Create a node.
$nid = $this->createEntity(['title' => 'First rev en title'], 'en');
$node = $storage->load($nid);
$original_revision_id = $node->getRevisionId();
// Add a French translation.
$translation = $node->addTranslation('fr');
$translation->title = 'First rev fr title';
$translation->setNewRevision(FALSE);
$translation->save();
// Create a new revision.
$node->title = 'Second rev en title';
$node->setNewRevision(TRUE);
$node->save();
// Get an English view of this revision.
$original_revision = $storage->loadRevision($original_revision_id);
$original_revision_url = $original_revision->toUrl('revision')->toString();
// Should be different from regular node URL.
$this->assertNotIdentical($original_revision_url, $original_revision->toUrl()->toString());
$this->drupalGet($original_revision_url);
$this->assertResponse(200);
// Contents should be in English, of correct revision.
$this->assertText('First rev en title');
$this->assertNoText('First rev fr title');
// Get a French view.
$url_fr = $original_revision->getTranslation('fr')->toUrl('revision')->toString();
// Should have different URL from English.
$this->assertNotIdentical($url_fr, $original_revision->toUrl()->toString());
$this->assertNotIdentical($url_fr, $original_revision_url);
$this->drupalGet($url_fr);
$this->assertResponse(200);
// Contents should be in French, of correct revision.
$this->assertText('First rev fr title');
$this->assertNoText('First rev en title');
}