Initial Drupal 11 with DDEV setup
This commit is contained in:
@ -0,0 +1,6 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies: { }
|
||||
id: test
|
||||
label: null
|
||||
description: null
|
||||
@ -0,0 +1,17 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- entity_test.entity_test_bundle.test
|
||||
module:
|
||||
- content_translation
|
||||
third_party_settings:
|
||||
content_translation:
|
||||
enabled: true
|
||||
bundle_settings:
|
||||
untranslatable_fields_hide: '0'
|
||||
id: entity_test_with_bundle.test
|
||||
target_entity_type_id: entity_test_with_bundle
|
||||
target_bundle: test
|
||||
default_langcode: site_default
|
||||
language_alterable: true
|
||||
@ -0,0 +1,9 @@
|
||||
name: 'Content translation tests'
|
||||
type: module
|
||||
description: 'Provides content translation tests.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
dependencies:
|
||||
- drupal:content_translation
|
||||
- drupal:language
|
||||
- drupal:entity_test
|
||||
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\content_translation_test\Entity;
|
||||
|
||||
use Drupal\Core\Entity\Attribute\ContentEntityType;
|
||||
use Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\entity_test\EntityTestForm;
|
||||
|
||||
/**
|
||||
* Defines the test entity class.
|
||||
*/
|
||||
#[ContentEntityType(
|
||||
id: 'entity_test_translatable_no_skip',
|
||||
label: new TranslatableMarkup('Test entity - Translatable check UI'),
|
||||
entity_keys: [
|
||||
'id' => 'id',
|
||||
'uuid' => 'uuid',
|
||||
'bundle' => 'type',
|
||||
'label' => 'name',
|
||||
'langcode' => 'langcode',
|
||||
],
|
||||
handlers: [
|
||||
'form' => ['default' => EntityTestForm::class],
|
||||
'route_provider' => [
|
||||
'html' => DefaultHtmlRouteProvider::class,
|
||||
],
|
||||
],
|
||||
links: [
|
||||
'edit-form' => '/entity_test_translatable_no_skip/{entity_test_translatable_no_skip}/edit',
|
||||
],
|
||||
admin_permission: 'administer entity_test content',
|
||||
base_table: 'entity_test_mul',
|
||||
data_table: 'entity_test_mul_property_data',
|
||||
translatable: TRUE,
|
||||
)]
|
||||
class EntityTestTranslatableNoUISkip extends EntityTest {
|
||||
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\content_translation_test\Entity;
|
||||
|
||||
use Drupal\Core\Entity\Attribute\ContentEntityType;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
|
||||
/**
|
||||
* Defines the test entity class.
|
||||
*/
|
||||
#[ContentEntityType(
|
||||
id: 'entity_test_translatable_UI_skip',
|
||||
label: new TranslatableMarkup('Test entity - Translatable skip UI check'),
|
||||
entity_keys: [
|
||||
'id' => 'id',
|
||||
'uuid' => 'uuid',
|
||||
'bundle' => 'type',
|
||||
'label' => 'name',
|
||||
'langcode' => 'langcode',
|
||||
],
|
||||
base_table: 'entity_test_mul',
|
||||
data_table: 'entity_test_mul_property_data',
|
||||
translatable: TRUE,
|
||||
additional: [
|
||||
'content_translation_ui_skip' => TRUE,
|
||||
],
|
||||
)]
|
||||
class EntityTestTranslatableUISkip extends EntityTest {
|
||||
|
||||
}
|
||||
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\content_translation_test\Hook;
|
||||
|
||||
use Drupal\Core\Access\AccessResultInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
|
||||
/**
|
||||
* Hook implementations for content_translation_test.
|
||||
*/
|
||||
class ContentTranslationTestHooks {
|
||||
|
||||
/**
|
||||
* Implements hook_entity_bundle_info_alter().
|
||||
*/
|
||||
#[Hook('entity_bundle_info_alter')]
|
||||
public function entityBundleInfoAlter(&$bundles): void {
|
||||
// Store the initial status of the "translatable" property for the
|
||||
// "entity_test_mul" bundle.
|
||||
$translatable = !empty($bundles['entity_test_mul']['entity_test_mul']['translatable']);
|
||||
\Drupal::state()->set('content_translation_test.translatable', $translatable);
|
||||
// Make it translatable if Content Translation did not. This will make the
|
||||
// entity object translatable even if it is disabled in Content Translation
|
||||
// settings.
|
||||
if (!$translatable) {
|
||||
$bundles['entity_test_mul']['entity_test_mul']['translatable'] = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_access().
|
||||
*/
|
||||
#[Hook('entity_access')]
|
||||
public function entityAccess(EntityInterface $entity, $operation, AccountInterface $account): AccessResultInterface {
|
||||
$access = \Drupal::state()->get('content_translation.entity_access.' . $entity->getEntityTypeId());
|
||||
if (!empty($access[$operation])) {
|
||||
return AccessResult::allowed();
|
||||
}
|
||||
else {
|
||||
return AccessResult::neutral();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_BASE_FORM_ID_alter().
|
||||
*
|
||||
* Adds a textfield to node forms based on a request parameter.
|
||||
*/
|
||||
#[Hook('form_node_form_alter')]
|
||||
public function formNodeFormAlter(&$form, FormStateInterface $form_state, $form_id) : void {
|
||||
$langcode = $form_state->getFormObject()->getFormLangcode($form_state);
|
||||
if (in_array($langcode, ['en', 'fr']) && \Drupal::request()->get('test_field_only_en_fr')) {
|
||||
$form['test_field_only_en_fr'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => 'Field only available on the english and french form',
|
||||
];
|
||||
foreach (array_keys($form['actions']) as $action) {
|
||||
if ($action != 'preview' && isset($form['actions'][$action]['#type']) && $form['actions'][$action]['#type'] === 'submit') {
|
||||
$form['actions'][$action]['#submit'][] = [$this, 'formNodeFormSubmit'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_translation_delete().
|
||||
*/
|
||||
#[Hook('entity_translation_delete')]
|
||||
public function entityTranslationDelete(EntityInterface $translation): void {
|
||||
\Drupal::state()->set('content_translation_test.translation_deleted', TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Form submission handler for custom field added based on a request parameter.
|
||||
*
|
||||
* @see content_translation_test_form_node_article_form_alter()
|
||||
*/
|
||||
public function formNodeFormSubmit($form, FormStateInterface $form_state): void {
|
||||
\Drupal::state()->set('test_field_only_en_fr', $form_state->getValue('test_field_only_en_fr'));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
name: 'Content translation test views'
|
||||
type: module
|
||||
description: 'Provides default views for views content translation tests.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
dependencies:
|
||||
- drupal:content_translation
|
||||
- drupal:views
|
||||
@ -0,0 +1,112 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- content_translation
|
||||
- user
|
||||
id: test_entity_translations_link
|
||||
label: People
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: users_field_data
|
||||
base_field: uid
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Default
|
||||
position: null
|
||||
display_options:
|
||||
access:
|
||||
type: none
|
||||
cache:
|
||||
type: tag
|
||||
query:
|
||||
type: views_query
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Filter
|
||||
reset_button: true
|
||||
reset_button_label: Reset
|
||||
pager:
|
||||
type: full
|
||||
options:
|
||||
items_per_page: 50
|
||||
style:
|
||||
type: table
|
||||
options:
|
||||
class: ''
|
||||
columns:
|
||||
name: name
|
||||
translation_link: translation_link
|
||||
default: created
|
||||
row:
|
||||
type: fields
|
||||
fields:
|
||||
name:
|
||||
id: name
|
||||
table: users_field_data
|
||||
field: name
|
||||
label: Username
|
||||
plugin_id: field
|
||||
type: user_name
|
||||
entity_type: user
|
||||
entity_field: name
|
||||
translation_link:
|
||||
id: translation_link
|
||||
table: users
|
||||
field: translation_link
|
||||
label: 'Translation link'
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
element_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
text: Translate
|
||||
plugin_id: content_translation_link
|
||||
entity_type: user
|
||||
filters:
|
||||
uid_raw:
|
||||
id: uid_raw
|
||||
table: users_field_data
|
||||
field: uid_raw
|
||||
operator: '!='
|
||||
value:
|
||||
value: '0'
|
||||
group: 1
|
||||
exposed: false
|
||||
plugin_id: numeric
|
||||
entity_type: user
|
||||
sorts:
|
||||
created:
|
||||
id: created
|
||||
table: users_field_data
|
||||
field: created
|
||||
order: DESC
|
||||
plugin_id: date
|
||||
entity_type: user
|
||||
entity_field: created
|
||||
title: People
|
||||
empty:
|
||||
area:
|
||||
id: area
|
||||
table: views
|
||||
field: area
|
||||
empty: true
|
||||
content:
|
||||
value: 'No people available.'
|
||||
format: plain_text
|
||||
plugin_id: text
|
||||
page_1:
|
||||
display_plugin: page
|
||||
id: page_1
|
||||
display_title: Page
|
||||
position: null
|
||||
display_options:
|
||||
path: test-entity-translations-link
|
||||
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Functional;
|
||||
|
||||
/**
|
||||
* Tests the test content translation UI with the test entity.
|
||||
*
|
||||
* @group content_translation
|
||||
* @group #slow
|
||||
*/
|
||||
class ContentTestTranslationUITest extends ContentTranslationUITestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $testHTMLEscapeForAllLanguages = TRUE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultCacheContexts = [
|
||||
'languages:language_interface',
|
||||
'theme',
|
||||
'url.query_args:_wrapper_format',
|
||||
'user.permissions',
|
||||
'url.site',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'language',
|
||||
'content_translation',
|
||||
'entity_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
// Use the entity_test_mul as this has multilingual property support.
|
||||
$this->entityTypeId = 'entity_test_mul_changed';
|
||||
parent::setUp();
|
||||
$this->doSetup();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getTranslatorPermissions(): array {
|
||||
return array_merge(parent::getTranslatorPermissions(), ['administer entity_test content', 'view test entity']);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Functional;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\content_translation\Traits\ContentTranslationTestTrait;
|
||||
|
||||
/**
|
||||
* Tests that contextual links are available for content translation.
|
||||
*
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationContextualLinksTest extends BrowserTestBase {
|
||||
|
||||
use ContentTranslationTestTrait;
|
||||
|
||||
/**
|
||||
* The bundle being tested.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $bundle;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The content type being tested.
|
||||
*
|
||||
* @var \Drupal\node\Entity\NodeType
|
||||
*/
|
||||
protected $contentType;
|
||||
|
||||
/**
|
||||
* The 'translator' user to use during testing.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $translator;
|
||||
|
||||
/**
|
||||
* The enabled languages.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $langcodes;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['content_translation', 'contextual', 'node'];
|
||||
|
||||
/**
|
||||
* The profile to install as a basis for testing.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $profile = 'testing';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
// Set up an additional language.
|
||||
$this->langcodes = [\Drupal::languageManager()->getDefaultLanguage()->getId(), 'es'];
|
||||
static::createLanguageFromLangcode('es');
|
||||
|
||||
// Create a content type.
|
||||
$this->bundle = $this->randomMachineName();
|
||||
$this->contentType = $this->drupalCreateContentType(['type' => $this->bundle]);
|
||||
|
||||
// Add a field to the content type. The field is not yet translatable.
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => 'field_test_text',
|
||||
'entity_type' => 'node',
|
||||
'type' => 'text',
|
||||
'cardinality' => 1,
|
||||
])->save();
|
||||
FieldConfig::create([
|
||||
'entity_type' => 'node',
|
||||
'field_name' => 'field_test_text',
|
||||
'bundle' => $this->bundle,
|
||||
'label' => 'Test text-field',
|
||||
])->save();
|
||||
$this->container->get('entity_display.repository')
|
||||
->getFormDisplay('node', $this->bundle)
|
||||
->setComponent('field_test_text', [
|
||||
'type' => 'text_textfield',
|
||||
'weight' => 0,
|
||||
])
|
||||
->save();
|
||||
|
||||
// Create a translator user.
|
||||
$permissions = [
|
||||
'access contextual links',
|
||||
'administer nodes',
|
||||
"edit any $this->bundle content",
|
||||
'translate any entity',
|
||||
];
|
||||
$this->translator = $this->drupalCreateUser($permissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a contextual link is available for translating a node.
|
||||
*/
|
||||
public function testContentTranslationContextualLinks(): void {
|
||||
// Create a node.
|
||||
$title = $this->randomString();
|
||||
$this->drupalCreateNode(['type' => $this->bundle, 'title' => $title, 'langcode' => 'en']);
|
||||
$node = $this->drupalGetNodeByTitle($title);
|
||||
|
||||
static::enableContentTranslation('node', $this->bundle);
|
||||
|
||||
// Check that the link leads to the translate page.
|
||||
$this->drupalLogin($this->translator);
|
||||
$translate_link = 'node/' . $node->id() . '/translations';
|
||||
$this->drupalGet($translate_link);
|
||||
$this->assertSession()->pageTextContains('Translations of ' . $node->label());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Test disabling content translation module.
|
||||
*
|
||||
* @covers \Drupal\language\Form\ContentLanguageSettingsForm
|
||||
* @covers ::_content_translation_form_language_content_settings_form_alter
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationDisableSettingTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'content_translation',
|
||||
'menu_link_content',
|
||||
'language',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests that entity schemas are up-to-date after enabling translation.
|
||||
*/
|
||||
public function testDisableSetting(): void {
|
||||
// Define selectors.
|
||||
$group_checkbox = 'entity_types[menu_link_content]';
|
||||
$translatable_checkbox = 'settings[menu_link_content][menu_link_content][translatable]';
|
||||
$language_alterable = 'settings[menu_link_content][menu_link_content][settings][language][language_alterable]';
|
||||
|
||||
$user = $this->drupalCreateUser([
|
||||
'administer site configuration',
|
||||
'administer content translation',
|
||||
'create content translations',
|
||||
'administer languages',
|
||||
]);
|
||||
$this->drupalLogin($user);
|
||||
|
||||
$assert = $this->assertSession();
|
||||
|
||||
$this->drupalGet('admin/config/regional/content-language');
|
||||
|
||||
$assert->checkboxNotChecked('entity_types[menu_link_content]');
|
||||
|
||||
$edit = [
|
||||
$group_checkbox => TRUE,
|
||||
$translatable_checkbox => TRUE,
|
||||
$language_alterable => TRUE,
|
||||
];
|
||||
$this->submitForm($edit, 'Save configuration');
|
||||
|
||||
$assert->statusMessageContains('Settings successfully updated.', 'status');
|
||||
|
||||
$assert->checkboxChecked($group_checkbox);
|
||||
|
||||
$edit = [
|
||||
$group_checkbox => FALSE,
|
||||
$translatable_checkbox => TRUE,
|
||||
$language_alterable => TRUE,
|
||||
];
|
||||
$this->submitForm($edit, 'Save configuration');
|
||||
|
||||
$assert->statusMessageContains('Settings successfully updated.', 'status');
|
||||
|
||||
$assert->checkboxNotChecked($group_checkbox);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\user\Entity\Role;
|
||||
|
||||
/**
|
||||
* Test enabling content translation module.
|
||||
*
|
||||
* @covers \Drupal\language\Form\ContentLanguageSettingsForm
|
||||
* @covers ::_content_translation_form_language_content_settings_form_alter
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationEnableTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['entity_test', 'menu_link_content', 'node'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests that entity schemas are up-to-date after enabling translation.
|
||||
*/
|
||||
public function testEnable(): void {
|
||||
$this->rootUser = $this->drupalCreateUser([
|
||||
'administer modules',
|
||||
'administer site configuration',
|
||||
'administer content types',
|
||||
]);
|
||||
$this->drupalLogin($this->rootUser);
|
||||
// Enable modules and make sure the related config entity type definitions
|
||||
// are installed.
|
||||
$edit = [
|
||||
'modules[content_translation][enable]' => TRUE,
|
||||
'modules[language][enable]' => TRUE,
|
||||
];
|
||||
$this->drupalGet('admin/modules');
|
||||
$this->submitForm($edit, 'Install');
|
||||
$this->rebuildContainer();
|
||||
|
||||
// Status messages are shown.
|
||||
$this->assertSession()->statusMessageContains('This site has only a single language enabled. Add at least one more language in order to translate content.', 'warning');
|
||||
$this->assertSession()->statusMessageContains('Enable translation for content types, taxonomy vocabularies, accounts, or any other element you wish to translate.', 'warning');
|
||||
|
||||
// No pending updates should be available.
|
||||
$this->drupalGet('admin/reports/status');
|
||||
$this->assertSession()->elementTextEquals('css', "details.system-status-report__entry summary:contains('Entity/field definitions') + div", 'Up to date');
|
||||
|
||||
$this->grantPermissions(Role::load(Role::AUTHENTICATED_ID), [
|
||||
'administer content translation',
|
||||
'administer languages',
|
||||
]);
|
||||
$this->drupalGet('admin/config/regional/content-language');
|
||||
// The node entity type should not be an option because it has no bundles.
|
||||
$this->assertSession()->responseNotContains('entity_types[node]');
|
||||
// Enable content translation on entity types that have will have a
|
||||
// content_translation_uid.
|
||||
$edit = [
|
||||
'entity_types[menu_link_content]' => TRUE,
|
||||
'settings[menu_link_content][menu_link_content][translatable]' => TRUE,
|
||||
'entity_types[entity_test_mul]' => TRUE,
|
||||
'settings[entity_test_mul][entity_test_mul][translatable]' => TRUE,
|
||||
];
|
||||
$this->submitForm($edit, 'Save configuration');
|
||||
|
||||
// No pending updates should be available.
|
||||
$this->drupalGet('admin/reports/status');
|
||||
$this->assertSession()->elementTextEquals('css', "details.system-status-report__entry summary:contains('Entity/field definitions') + div", 'Up to date');
|
||||
|
||||
// Create a node type and check the content translation settings are now
|
||||
// available for nodes.
|
||||
$edit = [
|
||||
'name' => 'foo',
|
||||
'title_label' => 'title for foo',
|
||||
'type' => 'foo',
|
||||
];
|
||||
$this->drupalGet('admin/structure/types/add');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->drupalGet('admin/config/regional/content-language');
|
||||
$this->assertSession()->responseContains('entity_types[node]');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the content translation behaviors on entity bundle UI.
|
||||
*
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationEntityBundleUITest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'language',
|
||||
'content_translation',
|
||||
'node',
|
||||
'comment',
|
||||
'field_ui',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$user = $this->drupalCreateUser([
|
||||
'access administration pages',
|
||||
'administer languages',
|
||||
'administer content translation',
|
||||
'administer content types',
|
||||
]);
|
||||
$this->drupalLogin($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests content types default translation behavior.
|
||||
*/
|
||||
public function testContentTypeUI(): void {
|
||||
// Create first content type.
|
||||
$this->drupalCreateContentType(['type' => 'article']);
|
||||
// Enable content translation.
|
||||
$edit = ['language_configuration[content_translation]' => TRUE];
|
||||
$this->drupalGet('admin/structure/types/manage/article');
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Make sure add page does not inherit translation configuration from first
|
||||
// content type.
|
||||
$this->drupalGet('admin/structure/types/add');
|
||||
$this->assertSession()->checkboxNotChecked('edit-language-configuration-content-translation');
|
||||
|
||||
// Create second content type and set content translation.
|
||||
$edit = [
|
||||
'name' => 'Page',
|
||||
'type' => 'page',
|
||||
'language_configuration[content_translation]' => TRUE,
|
||||
];
|
||||
$this->drupalGet('admin/structure/types/add');
|
||||
$this->submitForm($edit, 'Save and manage fields');
|
||||
|
||||
// Make sure the settings are saved when creating the content type.
|
||||
$this->drupalGet('admin/structure/types/manage/page');
|
||||
$this->assertSession()->checkboxChecked('edit-language-configuration-content-translation');
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Functional;
|
||||
|
||||
use Drupal\Tests\content_translation\Traits\ContentTranslationTestTrait;
|
||||
use Drupal\Tests\image\Kernel\ImageFieldCreationTrait;
|
||||
use Drupal\Tests\node\Functional\NodeTestBase;
|
||||
use Drupal\Tests\TestFileCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests the content translation language that is set.
|
||||
*
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationLanguageChangeTest extends NodeTestBase {
|
||||
|
||||
use ContentTranslationTestTrait;
|
||||
use ImageFieldCreationTrait;
|
||||
use TestFileCreationTrait {
|
||||
getTestFiles as drupalGetTestFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'language',
|
||||
'content_translation',
|
||||
'content_translation_test',
|
||||
'node',
|
||||
'block',
|
||||
'field_ui',
|
||||
'image',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$langcodes = ['de', 'fr'];
|
||||
foreach ($langcodes as $langcode) {
|
||||
static::createLanguageFromLangcode($langcode);
|
||||
}
|
||||
$this->drupalPlaceBlock('local_tasks_block');
|
||||
$user = $this->drupalCreateUser([
|
||||
'administer site configuration',
|
||||
'administer nodes',
|
||||
'create article content',
|
||||
'edit any article content',
|
||||
'delete any article content',
|
||||
'administer content translation',
|
||||
'translate any entity',
|
||||
'create content translations',
|
||||
'administer languages',
|
||||
'administer content types',
|
||||
'administer node fields',
|
||||
]);
|
||||
$this->drupalLogin($user);
|
||||
|
||||
// Enable translations for article.
|
||||
$this->enableContentTranslation('node', 'article');
|
||||
|
||||
$this->rebuildContainer();
|
||||
|
||||
$this->createImageField('field_image_field', 'node', 'article');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the source language is properly set when changing.
|
||||
*/
|
||||
public function testLanguageChange(): void {
|
||||
// Create a node in English.
|
||||
$this->drupalGet('node/add/article');
|
||||
$edit = [
|
||||
'title[0][value]' => 'english_title',
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Create a translation in French.
|
||||
$this->clickLink('Translate');
|
||||
$this->clickLink('Add');
|
||||
$this->submitForm([], 'Save (this translation)');
|
||||
$this->clickLink('Translate');
|
||||
|
||||
// Edit English translation.
|
||||
$this->clickLink('Edit', 1);
|
||||
// Upload and image after changing the node language.
|
||||
$images = $this->drupalGetTestFiles('image')[1];
|
||||
$edit = [
|
||||
'langcode[0][value]' => 'de',
|
||||
'files[field_image_field_0]' => $images->uri,
|
||||
];
|
||||
$this->submitForm($edit, 'Upload');
|
||||
$this->submitForm(['field_image_field[0][alt]' => 'alternative_text'], 'Save (this translation)');
|
||||
|
||||
// Check that the translation languages are correct.
|
||||
$node = $this->getNodeByTitle('english_title');
|
||||
$translation_languages = $node->getTranslationLanguages();
|
||||
$this->assertArrayHasKey('fr', $translation_languages);
|
||||
$this->assertArrayHasKey('de', $translation_languages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that title does not change on ajax call with new language value.
|
||||
*/
|
||||
public function testTitleDoesNotChangesOnChangingLanguageWidgetAndTriggeringAjaxCall(): void {
|
||||
// Create a node in English.
|
||||
$this->drupalGet('node/add/article', ['query' => ['test_field_only_en_fr' => 1]]);
|
||||
$edit = [
|
||||
'title[0][value]' => 'english_title',
|
||||
'test_field_only_en_fr' => 'node created',
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertEquals('node created', \Drupal::state()->get('test_field_only_en_fr'));
|
||||
|
||||
// Create a translation in French.
|
||||
$this->clickLink('Translate');
|
||||
$this->clickLink('Add');
|
||||
$this->submitForm([], 'Save (this translation)');
|
||||
$this->clickLink('Translate');
|
||||
|
||||
// Edit English translation.
|
||||
$node = $this->getNodeByTitle('english_title');
|
||||
$this->drupalGet('node/' . $node->id() . '/edit');
|
||||
// Test the expected title when loading the form.
|
||||
$this->assertSession()->titleEquals('Edit Article english_title | Drupal');
|
||||
// Upload and image after changing the node language.
|
||||
$images = $this->drupalGetTestFiles('image')[1];
|
||||
$edit = [
|
||||
'langcode[0][value]' => 'de',
|
||||
'files[field_image_field_0]' => $images->uri,
|
||||
];
|
||||
$this->submitForm($edit, 'Upload');
|
||||
// Test the expected title after triggering an ajax call with a new
|
||||
// language selected.
|
||||
$this->assertSession()->titleEquals('Edit Article english_title | Drupal');
|
||||
$edit = [
|
||||
'langcode[0][value]' => 'en',
|
||||
'field_image_field[0][alt]' => 'alternative_text',
|
||||
];
|
||||
$this->submitForm($edit, 'Save (this translation)');
|
||||
|
||||
// Check that the translation languages are correct.
|
||||
$node = $this->getNodeByTitle('english_title');
|
||||
$translation_languages = $node->getTranslationLanguages();
|
||||
$this->assertArrayHasKey('fr', $translation_languages);
|
||||
$this->assertArrayNotHasKey('de', $translation_languages);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Functional;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\entity_test\Entity\EntityTestMul;
|
||||
use Drupal\content_translation_test\Entity\EntityTestTranslatableNoUISkip;
|
||||
|
||||
/**
|
||||
* Tests whether canonical link tags are present for content entities.
|
||||
*
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationLinkTagTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'entity_test',
|
||||
'content_translation',
|
||||
'content_translation_test',
|
||||
'language',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The added languages.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $langcodes;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Set up user.
|
||||
$user = $this->drupalCreateUser([
|
||||
'view test entity',
|
||||
'view test entity translations',
|
||||
'administer entity_test content',
|
||||
]);
|
||||
$this->drupalLogin($user);
|
||||
|
||||
// Add additional languages.
|
||||
$this->langcodes = ['it', 'fr'];
|
||||
foreach ($this->langcodes as $langcode) {
|
||||
ConfigurableLanguage::createFromLangcode($langcode)->save();
|
||||
}
|
||||
|
||||
// Rebuild the container so that the new languages are picked up by services
|
||||
// that hold a list of languages.
|
||||
$this->rebuildContainer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a test entity with translations.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface
|
||||
* An entity with translations.
|
||||
*/
|
||||
protected function createTranslatableEntity(): EntityInterface {
|
||||
$entity = EntityTestMul::create(['label' => $this->randomString()]);
|
||||
|
||||
// Create translations for non default languages.
|
||||
foreach ($this->langcodes as $langcode) {
|
||||
$entity->addTranslation($langcode, ['label' => $this->randomString()]);
|
||||
}
|
||||
$entity->save();
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests alternate link tag found for entity types with canonical links.
|
||||
*/
|
||||
public function testCanonicalAlternateTags(): void {
|
||||
/** @var \Drupal\Core\Language\LanguageManagerInterface $languageManager */
|
||||
$languageManager = $this->container->get('language_manager');
|
||||
/** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager */
|
||||
$entityTypeManager = $this->container->get('entity_type.manager');
|
||||
|
||||
$definition = $entityTypeManager->getDefinition('entity_test_mul');
|
||||
$this->assertTrue($definition->hasLinkTemplate('canonical'), 'Canonical link template found for entity_test.');
|
||||
|
||||
$entity = $this->createTranslatableEntity();
|
||||
$url_base = $entity->toUrl('canonical')
|
||||
->setAbsolute();
|
||||
|
||||
$langcodes_all = $this->langcodes;
|
||||
$langcodes_all[] = $languageManager
|
||||
->getDefaultLanguage()
|
||||
->getId();
|
||||
|
||||
/** @var \Drupal\Core\Url[] $urls */
|
||||
$urls = array_map(
|
||||
function ($langcode) use ($url_base, $languageManager) {
|
||||
$url = clone $url_base;
|
||||
return $url
|
||||
->setOption('language', $languageManager->getLanguage($langcode));
|
||||
},
|
||||
array_combine($langcodes_all, $langcodes_all)
|
||||
);
|
||||
|
||||
// Ensure link tags for all languages are found on each language variation
|
||||
// page of an entity.
|
||||
foreach ($urls as $url) {
|
||||
$this->drupalGet($url);
|
||||
foreach ($urls as $langcode_alternate => $url_alternate) {
|
||||
$this->assertSession()->elementAttributeContains('xpath', "head/link[@rel='alternate' and @hreflang='$langcode_alternate']", 'href', $url_alternate->toString());
|
||||
}
|
||||
}
|
||||
|
||||
// Configure entity path as a front page.
|
||||
$entity_canonical = '/entity_test_mul/manage/' . $entity->id();
|
||||
$this->config('system.site')->set('page.front', $entity_canonical)->save();
|
||||
|
||||
// Tests hreflang when using entities as a front page.
|
||||
foreach ($urls as $url) {
|
||||
$this->drupalGet($url);
|
||||
foreach ($entity->getTranslationLanguages() as $language) {
|
||||
$frontpage_path = Url::fromRoute('<front>', [], [
|
||||
'absolute' => TRUE,
|
||||
'language' => $language,
|
||||
])->toString();
|
||||
$this->assertSession()->elementAttributeContains('xpath', "head/link[@rel='alternate' and @hreflang='{$language->getId()}']", 'href', $frontpage_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests alternate link tag missing for entity types without canonical links.
|
||||
*/
|
||||
public function testCanonicalAlternateTagsMissing(): void {
|
||||
/** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager */
|
||||
$entityTypeManager = $this->container->get('entity_type.manager');
|
||||
|
||||
$definition = $entityTypeManager->getDefinition('entity_test_translatable_no_skip');
|
||||
// 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.');
|
||||
|
||||
$entity = EntityTestTranslatableNoUISkip::create();
|
||||
$entity->save();
|
||||
$this->drupalGet($entity->toUrl('edit-form'));
|
||||
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->elementNotExists('xpath', '//link[@rel="alternate" and @hreflang]');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Functional;
|
||||
|
||||
/**
|
||||
* Tests the Content Translation metadata fields handling.
|
||||
*
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationMetadataFieldsTest extends ContentTranslationTestBase {
|
||||
|
||||
/**
|
||||
* The entity type being tested.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $entityTypeId = 'node';
|
||||
|
||||
/**
|
||||
* The bundle being tested.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $bundle = 'article';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['language', 'content_translation', 'node'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setupBundle(): void {
|
||||
parent::setupBundle();
|
||||
$this->createContentType(['type' => $this->bundle]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->doSetup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests skipping setting non translatable metadata fields.
|
||||
*/
|
||||
public function testSkipUntranslatable(): void {
|
||||
$this->drupalLogin($this->translator);
|
||||
$fields = \Drupal::service('entity_field.manager')->getFieldDefinitions($this->entityTypeId, $this->bundle);
|
||||
|
||||
// Turn off translatability for the metadata fields on the current bundle.
|
||||
$metadata_fields = ['created', 'changed', 'uid', 'status'];
|
||||
foreach ($metadata_fields as $field_name) {
|
||||
$fields[$field_name]
|
||||
->getConfig($this->bundle)
|
||||
->setTranslatable(FALSE)
|
||||
->save();
|
||||
}
|
||||
|
||||
// Create a new test entity with original values in the default language.
|
||||
$default_langcode = $this->langcodes[0];
|
||||
$entity_id = $this->createEntity(['title' => $this->randomString()], $default_langcode);
|
||||
$storage = \Drupal::entityTypeManager()->getStorage($this->entityTypeId);
|
||||
$storage->resetCache();
|
||||
$entity = $storage->load($entity_id);
|
||||
|
||||
// Add a content translation.
|
||||
$langcode = 'it';
|
||||
$values = $entity->toArray();
|
||||
// Apply a default value for the metadata fields.
|
||||
foreach ($metadata_fields as $field_name) {
|
||||
unset($values[$field_name]);
|
||||
}
|
||||
$entity->addTranslation($langcode, $values);
|
||||
|
||||
$metadata_source_translation = $this->manager->getTranslationMetadata($entity->getTranslation($default_langcode));
|
||||
$metadata_target_translation = $this->manager->getTranslationMetadata($entity->getTranslation($langcode));
|
||||
|
||||
$created_time = $metadata_source_translation->getCreatedTime();
|
||||
$changed_time = $metadata_source_translation->getChangedTime();
|
||||
$published = $metadata_source_translation->isPublished();
|
||||
$author = $metadata_source_translation->getAuthor();
|
||||
|
||||
$this->assertEquals($created_time, $metadata_target_translation->getCreatedTime(), 'Metadata created field has the same value for both translations.');
|
||||
$this->assertEquals($changed_time, $metadata_target_translation->getChangedTime(), 'Metadata changed field has the same value for both translations.');
|
||||
$this->assertEquals($published, $metadata_target_translation->isPublished(), 'Metadata published field has the same value for both translations.');
|
||||
$this->assertEquals($author->id(), $metadata_target_translation->getAuthor()->id(), 'Metadata author field has the same value for both translations.');
|
||||
|
||||
$metadata_target_translation->setCreatedTime(time() + 50);
|
||||
$metadata_target_translation->setChangedTime(time() + 50);
|
||||
$metadata_target_translation->setPublished(TRUE);
|
||||
$metadata_target_translation->setAuthor($this->editor);
|
||||
|
||||
$this->assertEquals($created_time, $metadata_target_translation->getCreatedTime(), 'Metadata created field correctly not updated');
|
||||
$this->assertEquals($changed_time, $metadata_target_translation->getChangedTime(), 'Metadata changed field correctly not updated');
|
||||
$this->assertEquals($published, $metadata_target_translation->isPublished(), 'Metadata published field correctly not updated');
|
||||
$this->assertEquals($author->id(), $metadata_target_translation->getAuthor()->id(), 'Metadata author field correctly not updated');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests setting translatable metadata fields.
|
||||
*/
|
||||
public function testSetTranslatable(): void {
|
||||
$this->drupalLogin($this->translator);
|
||||
$fields = \Drupal::service('entity_field.manager')->getFieldDefinitions($this->entityTypeId, $this->bundle);
|
||||
|
||||
// Turn off translatability for the metadata fields on the current bundle.
|
||||
$metadata_fields = ['created', 'changed', 'uid', 'status'];
|
||||
foreach ($metadata_fields as $field_name) {
|
||||
$fields[$field_name]
|
||||
->getConfig($this->bundle)
|
||||
->setTranslatable(TRUE)
|
||||
->save();
|
||||
}
|
||||
|
||||
// Create a new test entity with original values in the default language.
|
||||
$default_langcode = $this->langcodes[0];
|
||||
$entity_id = $this->createEntity(['title' => $this->randomString(), 'status' => FALSE], $default_langcode);
|
||||
$storage = \Drupal::entityTypeManager()->getStorage($this->entityTypeId);
|
||||
$storage->resetCache();
|
||||
$entity = $storage->load($entity_id);
|
||||
|
||||
// Add a content translation.
|
||||
$langcode = 'it';
|
||||
$values = $entity->toArray();
|
||||
// Apply a default value for the metadata fields.
|
||||
foreach ($metadata_fields as $field_name) {
|
||||
unset($values[$field_name]);
|
||||
}
|
||||
$entity->addTranslation($langcode, $values);
|
||||
|
||||
$metadata_source_translation = $this->manager->getTranslationMetadata($entity->getTranslation($default_langcode));
|
||||
$metadata_target_translation = $this->manager->getTranslationMetadata($entity->getTranslation($langcode));
|
||||
|
||||
$metadata_target_translation->setCreatedTime(time() + 50);
|
||||
$metadata_target_translation->setChangedTime(time() + 50);
|
||||
$metadata_target_translation->setPublished(TRUE);
|
||||
$metadata_target_translation->setAuthor($this->editor);
|
||||
|
||||
$this->assertNotEquals($metadata_source_translation->getCreatedTime(), $metadata_target_translation->getCreatedTime(), 'Metadata created field correctly different on both translations.');
|
||||
$this->assertNotEquals($metadata_source_translation->getChangedTime(), $metadata_target_translation->getChangedTime(), 'Metadata changed field correctly different on both translations.');
|
||||
$this->assertNotEquals($metadata_source_translation->isPublished(), $metadata_target_translation->isPublished(), 'Metadata published field correctly different on both translations.');
|
||||
$this->assertNotEquals($metadata_source_translation->getAuthor()->id(), $metadata_target_translation->getAuthor()->id(), 'Metadata author field correctly different on both translations.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Functional;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
|
||||
/**
|
||||
* Tests that new translations do not delete existing ones.
|
||||
*
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationNewTranslationWithExistingRevisionsTest extends ContentTranslationPendingRevisionTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'content_moderation',
|
||||
'content_translation',
|
||||
'content_translation_test',
|
||||
'language',
|
||||
'node',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->doSetup();
|
||||
$this->enableContentModeration();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a translation with a draft is not deleted.
|
||||
*/
|
||||
public function testDraftTranslationIsNotDeleted(): void {
|
||||
$this->drupalLogin($this->translator);
|
||||
|
||||
// Create a test node.
|
||||
$values = [
|
||||
'title' => "Test EN",
|
||||
'moderation_state' => 'published',
|
||||
];
|
||||
$id = $this->createEntity($values, 'en');
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
$entity = $this->storage->load($id);
|
||||
|
||||
// Add a published translation.
|
||||
$add_translation_url = Url::fromRoute("entity.{$this->entityTypeId}.content_translation_add",
|
||||
[
|
||||
$entity->getEntityTypeId() => $id,
|
||||
'source' => 'en',
|
||||
'target' => 'it',
|
||||
],
|
||||
[
|
||||
'language' => ConfigurableLanguage::load('it'),
|
||||
'absolute' => FALSE,
|
||||
]
|
||||
);
|
||||
$this->drupalGet($add_translation_url);
|
||||
$edit = [
|
||||
'title[0][value]' => "Test IT",
|
||||
'moderation_state[0][state]' => 'published',
|
||||
];
|
||||
$this->submitForm($edit, 'Save (this translation)');
|
||||
$it_revision = $this->loadRevisionTranslation($entity, 'it');
|
||||
|
||||
// Add a draft translation.
|
||||
$this->drupalGet($this->getEditUrl($it_revision));
|
||||
$edit = [
|
||||
'title[0][value]' => "Test IT 2",
|
||||
'moderation_state[0][state]' => 'draft',
|
||||
];
|
||||
$this->submitForm($edit, 'Save (this translation)');
|
||||
|
||||
// Add a new draft translation.
|
||||
$add_translation_url = Url::fromRoute("entity.{$this->entityTypeId}.content_translation_add",
|
||||
[
|
||||
$entity->getEntityTypeId() => $id,
|
||||
'source' => 'en',
|
||||
'target' => 'fr',
|
||||
],
|
||||
[
|
||||
'language' => ConfigurableLanguage::load('fr'),
|
||||
'absolute' => FALSE,
|
||||
]
|
||||
);
|
||||
$this->drupalGet($add_translation_url);
|
||||
$edit = [
|
||||
'title[0][value]' => "Test FR",
|
||||
'moderation_state[0][state]' => 'published',
|
||||
];
|
||||
$this->submitForm($edit, 'Save (this translation)');
|
||||
// Check the first translation still exists.
|
||||
$entity = $this->storage->loadUnchanged($id);
|
||||
$this->assertTrue($entity->hasTranslation('it'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test translation delete hooks are not invoked.
|
||||
*/
|
||||
public function testCreatingNewDraftDoesNotInvokeDeleteHook(): void {
|
||||
$this->drupalLogin($this->translator);
|
||||
|
||||
// Create a test node.
|
||||
$values = [
|
||||
'title' => "Test EN",
|
||||
'moderation_state' => 'published',
|
||||
];
|
||||
$id = $this->createEntity($values, 'en');
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
$entity = $this->storage->load($id);
|
||||
|
||||
// Add a published translation.
|
||||
$add_translation_url = Url::fromRoute("entity.{$this->entityTypeId}.content_translation_add",
|
||||
[
|
||||
$entity->getEntityTypeId() => $id,
|
||||
'source' => 'en',
|
||||
'target' => 'it',
|
||||
],
|
||||
[
|
||||
'language' => ConfigurableLanguage::load('it'),
|
||||
'absolute' => FALSE,
|
||||
]
|
||||
);
|
||||
$this->drupalGet($add_translation_url);
|
||||
$edit = [
|
||||
'title[0][value]' => "Test IT",
|
||||
'moderation_state[0][state]' => 'published',
|
||||
];
|
||||
$this->submitForm($edit, 'Save (this translation)');
|
||||
$it_revision = $this->loadRevisionTranslation($entity, 'it');
|
||||
|
||||
// Add a draft translation.
|
||||
$this->drupalGet($this->getEditUrl($it_revision));
|
||||
$edit = [
|
||||
'title[0][value]' => "Test IT 2",
|
||||
'moderation_state[0][state]' => 'draft',
|
||||
];
|
||||
$this->submitForm($edit, 'Save (this translation)');
|
||||
// Add a new draft translation.
|
||||
$add_translation_url = Url::fromRoute("entity.{$this->entityTypeId}.content_translation_add",
|
||||
[
|
||||
$entity->getEntityTypeId() => $id,
|
||||
'source' => 'en',
|
||||
'target' => 'fr',
|
||||
],
|
||||
[
|
||||
'language' => ConfigurableLanguage::load('fr'),
|
||||
'absolute' => FALSE,
|
||||
]
|
||||
);
|
||||
$this->drupalGet($add_translation_url);
|
||||
$edit = [
|
||||
'title[0][value]' => "Test FR",
|
||||
'moderation_state[0][state]' => 'draft',
|
||||
];
|
||||
$this->submitForm($edit, 'Save (this translation)');
|
||||
// If the translation delete hook was incorrectly invoked, the state
|
||||
// variable would be set.
|
||||
$this->assertNull($this->container->get('state')->get('content_translation_test.translation_deleted'));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Functional;
|
||||
|
||||
use Drupal\Tests\language\Traits\LanguageTestTrait;
|
||||
use Drupal\Tests\node\Functional\NodeTestBase;
|
||||
use Drupal\user\Entity\Role;
|
||||
|
||||
/**
|
||||
* Tests the content translation operations available in the content listing.
|
||||
*
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationOperationsTest extends NodeTestBase {
|
||||
|
||||
use LanguageTestTrait;
|
||||
|
||||
/**
|
||||
* A base user.
|
||||
*
|
||||
* @var \Drupal\user\Entity\User|false
|
||||
*/
|
||||
protected $baseUser1;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* A base user.
|
||||
*
|
||||
* @var \Drupal\user\Entity\User|false
|
||||
*/
|
||||
protected $baseUser2;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'language',
|
||||
'content_translation',
|
||||
'node',
|
||||
'views',
|
||||
'block',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Enable additional languages.
|
||||
$langcodes = ['es', 'ast'];
|
||||
foreach ($langcodes as $langcode) {
|
||||
static::createLanguageFromLangcode($langcode);
|
||||
}
|
||||
|
||||
// Enable translation for the current entity type and ensure the change is
|
||||
// picked up.
|
||||
\Drupal::service('content_translation.manager')->setEnabled('node', 'article', TRUE);
|
||||
|
||||
$this->baseUser1 = $this->drupalCreateUser(['access content overview']);
|
||||
$this->baseUser2 = $this->drupalCreateUser([
|
||||
'access content overview',
|
||||
'create content translations',
|
||||
'update content translations',
|
||||
'delete content translations',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the operation "Translate" is displayed in the content listing.
|
||||
*/
|
||||
public function testOperationTranslateLink(): void {
|
||||
$node = $this->drupalCreateNode(['type' => 'article', 'langcode' => 'es']);
|
||||
// Verify no translation operation links are displayed for users without
|
||||
// permission.
|
||||
$this->drupalLogin($this->baseUser1);
|
||||
$this->drupalGet('admin/content');
|
||||
$this->assertSession()->linkByHrefNotExists('node/' . $node->id() . '/translations');
|
||||
$this->drupalLogout();
|
||||
// Verify there's a translation operation link for users with enough
|
||||
// permissions.
|
||||
$this->drupalLogin($this->baseUser2);
|
||||
$this->drupalGet('admin/content');
|
||||
$this->assertSession()->linkByHrefExists('node/' . $node->id() . '/translations');
|
||||
|
||||
// Ensure that an unintended misconfiguration of permissions does not open
|
||||
// access to the translation form, see https://www.drupal.org/node/2558905.
|
||||
$this->drupalLogout();
|
||||
user_role_change_permissions(
|
||||
Role::AUTHENTICATED_ID,
|
||||
[
|
||||
'create content translations' => TRUE,
|
||||
'access content' => FALSE,
|
||||
]
|
||||
);
|
||||
$this->drupalLogin($this->baseUser1);
|
||||
$this->drupalGet($node->toUrl('drupal:content-translation-overview'));
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
|
||||
// Ensure that the translation overview is also not accessible when the user
|
||||
// has 'access content', but the node is not published.
|
||||
user_role_change_permissions(
|
||||
Role::AUTHENTICATED_ID,
|
||||
[
|
||||
'create content translations' => TRUE,
|
||||
'access content' => TRUE,
|
||||
]
|
||||
);
|
||||
$node->setUnpublished()->save();
|
||||
$this->drupalGet($node->toUrl('drupal:content-translation-overview'));
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
$this->drupalLogout();
|
||||
|
||||
// Ensure the 'Translate' local task does not show up anymore when disabling
|
||||
// translations for a content type.
|
||||
$node->setPublished()->save();
|
||||
user_role_change_permissions(
|
||||
Role::AUTHENTICATED_ID,
|
||||
[
|
||||
'administer content translation' => TRUE,
|
||||
'administer languages' => TRUE,
|
||||
]
|
||||
);
|
||||
$this->drupalPlaceBlock('local_tasks_block');
|
||||
$this->drupalLogin($this->baseUser2);
|
||||
$this->drupalGet('node/' . $node->id());
|
||||
$this->assertSession()->linkByHrefExists('node/' . $node->id() . '/translations');
|
||||
static::disableBundleTranslation('node', 'article');
|
||||
$this->drupalGet('node/' . $node->id());
|
||||
$this->assertSession()->linkByHrefNotExists('node/' . $node->id() . '/translations');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the access to the overview page for translations.
|
||||
*
|
||||
* @see content_translation_translate_access()
|
||||
*/
|
||||
public function testContentTranslationOverviewAccess(): void {
|
||||
$access_control_handler = \Drupal::entityTypeManager()->getAccessControlHandler('node');
|
||||
$user = $this->createUser(['create content translations', 'access content']);
|
||||
$this->drupalLogin($user);
|
||||
|
||||
$node = $this->drupalCreateNode(['status' => FALSE, 'type' => 'article']);
|
||||
$this->assertFalse(content_translation_translate_access($node)->isAllowed());
|
||||
$access_control_handler->resetCache();
|
||||
|
||||
$node->setPublished();
|
||||
$node->save();
|
||||
$this->assertTrue(content_translation_translate_access($node)->isAllowed());
|
||||
$access_control_handler->resetCache();
|
||||
|
||||
user_role_change_permissions(
|
||||
Role::AUTHENTICATED_ID,
|
||||
[
|
||||
'access content' => FALSE,
|
||||
]
|
||||
);
|
||||
|
||||
$user = $this->createUser(['create content translations']);
|
||||
$this->drupalLogin($user);
|
||||
$this->assertFalse(content_translation_translate_access($node)->isAllowed());
|
||||
$access_control_handler->resetCache();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Functional;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
|
||||
/**
|
||||
* Tests the "Flag as outdated" functionality with revision translations.
|
||||
*
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationOutdatedRevisionTranslationTest extends ContentTranslationPendingRevisionTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->doSetup();
|
||||
$this->enableContentModeration();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that outdated revision translations work correctly.
|
||||
*/
|
||||
public function testFlagAsOutdatedHidden(): void {
|
||||
// Create a test node.
|
||||
$values = [
|
||||
'title' => 'Test 1.1 EN',
|
||||
'moderation_state' => 'published',
|
||||
];
|
||||
$id = $this->createEntity($values, 'en');
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
$entity = $this->storage->load($id);
|
||||
|
||||
// Add a published Italian translation.
|
||||
$add_translation_url = Url::fromRoute("entity.{$this->entityTypeId}.content_translation_add",
|
||||
[
|
||||
$entity->getEntityTypeId() => $id,
|
||||
'source' => 'en',
|
||||
'target' => 'it',
|
||||
],
|
||||
[
|
||||
'language' => ConfigurableLanguage::load('it'),
|
||||
'absolute' => FALSE,
|
||||
]
|
||||
);
|
||||
$this->drupalGet($add_translation_url);
|
||||
$this->assertFlagWidget();
|
||||
$edit = [
|
||||
'title[0][value]' => 'Test 1.2 IT',
|
||||
'moderation_state[0][state]' => 'published',
|
||||
];
|
||||
$this->submitForm($edit, 'Save (this translation)');
|
||||
|
||||
// Add a published French translation.
|
||||
$add_translation_url = Url::fromRoute("entity.{$this->entityTypeId}.content_translation_add",
|
||||
[
|
||||
$entity->getEntityTypeId() => $id,
|
||||
'source' => 'en',
|
||||
'target' => 'fr',
|
||||
],
|
||||
[
|
||||
'language' => ConfigurableLanguage::load('fr'),
|
||||
'absolute' => FALSE,
|
||||
]
|
||||
);
|
||||
$this->drupalGet($add_translation_url);
|
||||
$this->assertFlagWidget();
|
||||
$edit = [
|
||||
'title[0][value]' => 'Test 1.3 FR',
|
||||
'moderation_state[0][state]' => 'published',
|
||||
];
|
||||
$this->submitForm($edit, 'Save (this translation)');
|
||||
|
||||
// Create an English draft.
|
||||
$entity = $this->storage->loadUnchanged($id);
|
||||
$en_edit_url = $this->getEditUrl($entity);
|
||||
$this->drupalGet($en_edit_url);
|
||||
$this->assertFlagWidget();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the flag widget is displayed.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertFlagWidget(): void {
|
||||
$this->assertSession()->pageTextNotContains('Flag other translations as outdated');
|
||||
$this->assertSession()->pageTextContains('Translations cannot be flagged as outdated when content is moderated.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,192 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Functional;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Tests\content_moderation\Traits\ContentModerationTestTrait;
|
||||
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
|
||||
|
||||
/**
|
||||
* Base class for pending revision translation tests.
|
||||
*/
|
||||
abstract class ContentTranslationPendingRevisionTestBase extends ContentTranslationTestBase {
|
||||
|
||||
use ContentTypeCreationTrait;
|
||||
use ContentModerationTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'language',
|
||||
'content_translation',
|
||||
'content_moderation',
|
||||
'node',
|
||||
];
|
||||
|
||||
/**
|
||||
* The entity storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\ContentEntityStorageInterface
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* Permissions common to all test accounts.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $commonPermissions;
|
||||
|
||||
/**
|
||||
* The current test account.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $currentAccount;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
$this->entityTypeId = 'node';
|
||||
$this->bundle = 'article';
|
||||
|
||||
$this->commonPermissions = [
|
||||
'view any unpublished content',
|
||||
"translate {$this->bundle} {$this->entityTypeId}",
|
||||
"create content translations",
|
||||
'use editorial transition create_new_draft',
|
||||
'use editorial transition publish',
|
||||
'use editorial transition archive',
|
||||
'use editorial transition archived_draft',
|
||||
'use editorial transition archived_published',
|
||||
];
|
||||
|
||||
parent::setUp();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */
|
||||
$entity_type_manager = $this->container->get('entity_type.manager');
|
||||
$this->storage = $entity_type_manager->getStorage($this->entityTypeId);
|
||||
|
||||
// @todo Remove this line once https://www.drupal.org/node/2945928 is fixed.
|
||||
$this->config('node.settings')->set('use_admin_theme', '1')->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables content moderation for the test entity type and bundle.
|
||||
*/
|
||||
protected function enableContentModeration() {
|
||||
$perms = array_merge(parent::getAdministratorPermissions(), [
|
||||
'administer workflows',
|
||||
'view latest version',
|
||||
]);
|
||||
$this->rootUser = $this->drupalCreateUser($perms);
|
||||
$this->drupalLogin($this->rootUser);
|
||||
$workflow_id = 'editorial';
|
||||
$this->drupalGet('/admin/config/workflow/workflows');
|
||||
$edit['bundles[' . $this->bundle . ']'] = TRUE;
|
||||
$this->drupalGet('admin/config/workflow/workflows/manage/' . $workflow_id . '/type/' . $this->entityTypeId);
|
||||
$this->submitForm($edit, 'Save');
|
||||
// Ensure the parent environment is up-to-date.
|
||||
// @see content_moderation_workflow_insert()
|
||||
\Drupal::service('entity_type.bundle.info')->clearCachedBundles();
|
||||
\Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
|
||||
/** @var \Drupal\Core\Routing\RouteBuilderInterface $router_builder */
|
||||
$router_builder = $this->container->get('router.builder');
|
||||
$router_builder->rebuildIfNeeded();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEditorPermissions() {
|
||||
$editor_permissions = [
|
||||
"edit any {$this->bundle} content",
|
||||
"delete any {$this->bundle} content",
|
||||
"view {$this->bundle} revisions",
|
||||
"delete {$this->bundle} revisions",
|
||||
];
|
||||
return array_merge($editor_permissions, $this->commonPermissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getTranslatorPermissions() {
|
||||
return array_merge(parent::getTranslatorPermissions(), $this->commonPermissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setupBundle() {
|
||||
parent::setupBundle();
|
||||
$this->createContentType(['type' => $this->bundle]);
|
||||
$this->createEditorialWorkflow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the active revision translation for the specified entity.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
|
||||
* The entity being edited.
|
||||
* @param string $langcode
|
||||
* The translation language code.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\ContentEntityInterface|null
|
||||
* The active revision translation or NULL if none could be identified.
|
||||
*/
|
||||
protected function loadRevisionTranslation(ContentEntityInterface $entity, $langcode) {
|
||||
// Explicitly invalidate the cache for that node, as the call below is
|
||||
// statically cached.
|
||||
$this->storage->resetCache([$entity->id()]);
|
||||
$revision_id = $this->storage->getLatestTranslationAffectedRevisionId($entity->id(), $langcode);
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $revision */
|
||||
$revision = $revision_id ? $this->storage->loadRevision($revision_id) : NULL;
|
||||
return $revision && $revision->hasTranslation($langcode) ? $revision->getTranslation($langcode) : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the edit URL for the specified entity.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
|
||||
* The entity being edited.
|
||||
*
|
||||
* @return \Drupal\Core\Url
|
||||
* The edit URL.
|
||||
*/
|
||||
protected function getEditUrl(ContentEntityInterface $entity) {
|
||||
if ($entity->access('update', $this->loggedInUser)) {
|
||||
$url = $entity->toUrl('edit-form');
|
||||
}
|
||||
else {
|
||||
$url = $entity->toUrl('drupal:content-translation-edit');
|
||||
$url->setRouteParameter('language', $entity->language()->getId());
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the delete translation URL for the specified entity.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
|
||||
* The entity being edited.
|
||||
*
|
||||
* @return \Drupal\Core\Url
|
||||
* The delete translation URL.
|
||||
*/
|
||||
protected function getDeleteUrl(ContentEntityInterface $entity) {
|
||||
if ($entity->access('delete', $this->loggedInUser)) {
|
||||
$url = $entity->toUrl('delete-form');
|
||||
}
|
||||
else {
|
||||
$url = $entity->toUrl('drupal:content-translation-delete');
|
||||
$url->setRouteParameter('language', $entity->language()->getId());
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,227 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Functional;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
|
||||
/**
|
||||
* Tests that revision translation deletion is handled correctly.
|
||||
*
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationRevisionTranslationDeletionTest extends ContentTranslationPendingRevisionTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->doSetup();
|
||||
$this->enableContentModeration();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that translation overview handles pending revisions correctly.
|
||||
*/
|
||||
public function testOverview(): void {
|
||||
$index = 1;
|
||||
$accounts = [
|
||||
$this->rootUser,
|
||||
$this->editor,
|
||||
$this->translator,
|
||||
];
|
||||
foreach ($accounts as $account) {
|
||||
$this->currentAccount = $account;
|
||||
$this->doTestOverview($index++);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a test run.
|
||||
*
|
||||
* @param int $index
|
||||
* The test run index.
|
||||
*/
|
||||
public function doTestOverview($index): void {
|
||||
$this->drupalLogin($this->currentAccount);
|
||||
|
||||
// Create a test node.
|
||||
$values = [
|
||||
'title' => "Test $index.1 EN",
|
||||
'moderation_state' => 'published',
|
||||
];
|
||||
$id = $this->createEntity($values, 'en');
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
$entity = $this->storage->load($id);
|
||||
|
||||
// Add a draft translation and check that it is available only in the latest
|
||||
// revision.
|
||||
$add_translation_url = Url::fromRoute("entity.{$this->entityTypeId}.content_translation_add",
|
||||
[
|
||||
$entity->getEntityTypeId() => $id,
|
||||
'source' => 'en',
|
||||
'target' => 'it',
|
||||
],
|
||||
[
|
||||
'language' => ConfigurableLanguage::load('it'),
|
||||
'absolute' => FALSE,
|
||||
]
|
||||
);
|
||||
$add_translation_href = $add_translation_url->toString();
|
||||
$this->drupalGet($add_translation_url);
|
||||
$edit = [
|
||||
'title[0][value]' => "Test $index.2 IT",
|
||||
'moderation_state[0][state]' => 'draft',
|
||||
];
|
||||
$this->submitForm($edit, 'Save (this translation)');
|
||||
$entity = $this->storage->loadUnchanged($id);
|
||||
$this->assertFalse($entity->hasTranslation('it'));
|
||||
$it_revision = $this->loadRevisionTranslation($entity, 'it');
|
||||
$this->assertTrue($it_revision->hasTranslation('it'));
|
||||
|
||||
// Check that translations cannot be deleted in drafts.
|
||||
$overview_url = $entity->toUrl('drupal:content-translation-overview');
|
||||
$this->drupalGet($overview_url);
|
||||
$it_delete_url = $this->getDeleteUrl($it_revision);
|
||||
$it_delete_href = $it_delete_url->toString();
|
||||
$this->assertSession()->linkByHrefNotExists($it_delete_href);
|
||||
$warning = 'The "Delete translation" action is only available for published translations.';
|
||||
$this->assertSession()->statusMessageContains($warning, 'warning');
|
||||
$this->drupalGet($this->getEditUrl($it_revision));
|
||||
$this->assertSession()->linkNotExistsExact('Delete translation');
|
||||
|
||||
// Publish the translation and verify it can be deleted.
|
||||
$edit = [
|
||||
'title[0][value]' => "Test $index.3 IT",
|
||||
'moderation_state[0][state]' => 'published',
|
||||
];
|
||||
$this->submitForm($edit, 'Save (this translation)');
|
||||
$entity = $this->storage->loadUnchanged($id);
|
||||
$this->assertTrue($entity->hasTranslation('it'));
|
||||
$it_revision = $this->loadRevisionTranslation($entity, 'it');
|
||||
$this->assertTrue($it_revision->hasTranslation('it'));
|
||||
$this->drupalGet($overview_url);
|
||||
$this->assertSession()->linkByHrefExists($it_delete_href);
|
||||
$this->assertSession()->statusMessageNotContains($warning);
|
||||
$this->drupalGet($this->getEditUrl($it_revision));
|
||||
$this->assertSession()->linkExistsExact('Delete translation');
|
||||
|
||||
// Create an English draft and verify the published translation was
|
||||
// preserved.
|
||||
$this->drupalLogin($this->editor);
|
||||
$en_revision = $this->loadRevisionTranslation($entity, 'en');
|
||||
$this->drupalGet($this->getEditUrl($en_revision));
|
||||
$edit = [
|
||||
'title[0][value]' => "Test $index.4 EN",
|
||||
'moderation_state[0][state]' => 'draft',
|
||||
];
|
||||
$this->submitForm($edit, 'Save (this translation)');
|
||||
$entity = $this->storage->loadUnchanged($id);
|
||||
$this->assertTrue($entity->hasTranslation('it'));
|
||||
$en_revision = $this->loadRevisionTranslation($entity, 'en');
|
||||
$this->assertTrue($en_revision->hasTranslation('it'));
|
||||
$this->drupalLogin($this->currentAccount);
|
||||
|
||||
// Delete the translation and verify that it is actually gone and that it is
|
||||
// possible to create it again.
|
||||
$this->drupalGet($it_delete_url);
|
||||
$this->submitForm([], 'Delete Italian translation');
|
||||
$entity = $this->storage->loadUnchanged($id);
|
||||
$this->assertFalse($entity->hasTranslation('it'));
|
||||
$it_revision = $this->loadRevisionTranslation($entity, 'it');
|
||||
$this->assertTrue($it_revision->wasDefaultRevision());
|
||||
$this->assertTrue($it_revision->hasTranslation('it'));
|
||||
$this->assertLessThan($entity->getRevisionId(), $it_revision->getRevisionId());
|
||||
$this->drupalGet($overview_url);
|
||||
$this->assertSession()->linkByHrefNotExists($this->getEditUrl($it_revision)->toString());
|
||||
$this->assertSession()->linkByHrefExists($add_translation_href);
|
||||
|
||||
// Publish the English draft and verify the translation is not accidentally
|
||||
// restored.
|
||||
$this->drupalLogin($this->editor);
|
||||
$en_revision = $this->loadRevisionTranslation($entity, 'en');
|
||||
$this->drupalGet($this->getEditUrl($en_revision));
|
||||
$edit = [
|
||||
'title[0][value]' => "Test $index.6 EN",
|
||||
'moderation_state[0][state]' => 'published',
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$entity = $this->storage->loadUnchanged($id);
|
||||
$this->assertFalse($entity->hasTranslation('it'));
|
||||
$this->drupalLogin($this->currentAccount);
|
||||
|
||||
// Create a published translation again and verify it could be deleted.
|
||||
$this->drupalGet($add_translation_url);
|
||||
$edit = [
|
||||
'title[0][value]' => "Test $index.7 IT",
|
||||
'moderation_state[0][state]' => 'published',
|
||||
];
|
||||
$this->submitForm($edit, 'Save (this translation)');
|
||||
$entity = $this->storage->loadUnchanged($id);
|
||||
$this->assertTrue($entity->hasTranslation('it'));
|
||||
$it_revision = $this->loadRevisionTranslation($entity, 'it');
|
||||
$this->assertTrue($it_revision->hasTranslation('it'));
|
||||
$this->drupalGet($overview_url);
|
||||
$this->assertSession()->linkByHrefExists($it_delete_href);
|
||||
|
||||
// Create a translation draft again and verify it cannot be deleted.
|
||||
$this->drupalGet($this->getEditUrl($it_revision));
|
||||
$edit = [
|
||||
'title[0][value]' => "Test $index.8 IT",
|
||||
'moderation_state[0][state]' => 'draft',
|
||||
];
|
||||
$this->submitForm($edit, 'Save (this translation)');
|
||||
$entity = $this->storage->loadUnchanged($id);
|
||||
$this->assertTrue($entity->hasTranslation('it'));
|
||||
$it_revision = $this->loadRevisionTranslation($entity, 'it');
|
||||
$this->assertTrue($it_revision->hasTranslation('it'));
|
||||
$this->drupalGet($overview_url);
|
||||
$this->assertSession()->linkByHrefNotExists($it_delete_href);
|
||||
|
||||
// Delete the translation draft and verify the translation can be deleted
|
||||
// again, since the active revision is now a default revision.
|
||||
$this->drupalLogin($this->editor);
|
||||
$this->drupalGet($it_revision->toUrl('version-history'));
|
||||
$revision_deletion_url = Url::fromRoute('node.revision_delete_confirm',
|
||||
[
|
||||
'node' => $id,
|
||||
'node_revision' => $it_revision->getRevisionId(),
|
||||
],
|
||||
[
|
||||
'language' => ConfigurableLanguage::load('it'),
|
||||
'absolute' => FALSE,
|
||||
]
|
||||
);
|
||||
$revision_deletion_href = $revision_deletion_url->toString();
|
||||
$this->getSession()->getDriver()->click("//a[@href='$revision_deletion_href']");
|
||||
$this->submitForm([], 'Delete');
|
||||
$this->drupalLogin($this->currentAccount);
|
||||
$this->drupalGet($overview_url);
|
||||
$this->assertSession()->linkByHrefExists($it_delete_href);
|
||||
|
||||
// Verify that now the translation can be deleted.
|
||||
$this->drupalGet($this->getEditUrl($it_revision)->setOption('query', ['destination', '/kittens']));
|
||||
$this->clickLink('Delete translation');
|
||||
$this->submitForm([], 'Delete Italian translation');
|
||||
$this->assertStringEndsWith('/kittens', $this->getSession()->getCurrentUrl());
|
||||
|
||||
$entity = $this->storage->loadUnchanged($id);
|
||||
$this->assertFalse($entity->hasTranslation('it'));
|
||||
$it_revision = $this->loadRevisionTranslation($entity, 'it');
|
||||
$this->assertTrue($it_revision->wasDefaultRevision());
|
||||
$this->assertTrue($it_revision->hasTranslation('it'));
|
||||
$this->assertLessThan($entity->getRevisionId(), $it_revision->getRevisionId());
|
||||
$this->drupalGet($overview_url);
|
||||
$this->assertSession()->linkByHrefNotExists($this->getEditUrl($it_revision)->toString());
|
||||
$this->assertSession()->linkByHrefExists($add_translation_href);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,331 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Functional;
|
||||
|
||||
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
|
||||
use Drupal\comment\Tests\CommentTestTrait;
|
||||
use Drupal\Core\Field\Entity\BaseFieldOverride;
|
||||
use Drupal\Core\Language\Language;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\language\Entity\ContentLanguageSettings;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
|
||||
|
||||
/**
|
||||
* Tests the content translation settings UI.
|
||||
*
|
||||
* @covers \Drupal\language\Form\ContentLanguageSettingsForm
|
||||
* @covers ::_content_translation_form_language_content_settings_form_alter
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationSettingsTest extends BrowserTestBase {
|
||||
|
||||
use CommentTestTrait;
|
||||
use FieldUiTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'language',
|
||||
'content_translation',
|
||||
'node',
|
||||
'comment',
|
||||
'field_ui',
|
||||
'entity_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Set up two content types to test fields shared between different
|
||||
// bundles.
|
||||
$this->drupalCreateContentType(['type' => 'article']);
|
||||
$this->drupalCreateContentType(['type' => 'page']);
|
||||
$this->addDefaultCommentField('node', 'article', 'comment_article', CommentItemInterface::OPEN, 'comment_article');
|
||||
$this->addDefaultCommentField('node', 'page', 'comment_page');
|
||||
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'access administration pages',
|
||||
'administer languages',
|
||||
'administer content translation',
|
||||
'administer content types',
|
||||
'administer node fields',
|
||||
'administer comment fields',
|
||||
'administer comments',
|
||||
'administer comment types',
|
||||
'administer account settings',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the settings UI works as expected.
|
||||
*/
|
||||
public function testSettingsUI(): void {
|
||||
// Check for the content_translation_menu_links_discovered_alter() changes.
|
||||
$this->drupalGet('admin/config');
|
||||
$this->assertSession()->linkExists('Content language and translation');
|
||||
$this->assertSession()->pageTextContains('Configure language and translation support for content.');
|
||||
// Test that the translation settings are ignored if the bundle is marked
|
||||
// translatable but the entity type is not.
|
||||
$edit = ['settings[comment][comment_article][translatable]' => TRUE];
|
||||
$this->assertSettings('comment', 'comment_article', FALSE, $edit);
|
||||
|
||||
// Test that the translation settings are ignored if only a field is marked
|
||||
// as translatable and not the related entity type and bundle.
|
||||
$edit = ['settings[comment][comment_article][fields][comment_body]' => TRUE];
|
||||
$this->assertSettings('comment', 'comment_article', FALSE, $edit);
|
||||
|
||||
// Test that the translation settings are not stored if an entity type and
|
||||
// bundle are marked as translatable but no field is.
|
||||
$edit = [
|
||||
'entity_types[comment]' => TRUE,
|
||||
'settings[comment][comment_article][translatable]' => TRUE,
|
||||
// Base fields are translatable by default.
|
||||
'settings[comment][comment_article][fields][changed]' => FALSE,
|
||||
'settings[comment][comment_article][fields][created]' => FALSE,
|
||||
'settings[comment][comment_article][fields][homepage]' => FALSE,
|
||||
'settings[comment][comment_article][fields][hostname]' => FALSE,
|
||||
'settings[comment][comment_article][fields][mail]' => FALSE,
|
||||
'settings[comment][comment_article][fields][name]' => FALSE,
|
||||
'settings[comment][comment_article][fields][status]' => FALSE,
|
||||
'settings[comment][comment_article][fields][subject]' => FALSE,
|
||||
'settings[comment][comment_article][fields][uid]' => FALSE,
|
||||
];
|
||||
$this->assertSettings('comment', 'comment_article', FALSE, $edit);
|
||||
$this->assertSession()->statusMessageContains('At least one field needs to be translatable to enable Comment_article for translation.', 'error');
|
||||
|
||||
// Test that the translation settings are not stored if a non-configurable
|
||||
// language is set as default and the language selector is hidden.
|
||||
$edit = [
|
||||
'entity_types[comment]' => TRUE,
|
||||
'settings[comment][comment_article][settings][language][langcode]' => Language::LANGCODE_NOT_SPECIFIED,
|
||||
'settings[comment][comment_article][settings][language][language_alterable]' => FALSE,
|
||||
'settings[comment][comment_article][translatable]' => TRUE,
|
||||
'settings[comment][comment_article][fields][comment_body]' => TRUE,
|
||||
];
|
||||
$this->assertSettings('comment', 'comment_article', FALSE, $edit);
|
||||
$this->assertSession()->statusMessageContains('Translation is not supported if language is always one of: Not specified, Not applicable', 'error');
|
||||
|
||||
// Test that a field shared among different bundles can be enabled without
|
||||
// needing to make all the related bundles translatable.
|
||||
$edit = [
|
||||
'entity_types[comment]' => TRUE,
|
||||
'settings[comment][comment_article][settings][language][langcode]' => 'current_interface',
|
||||
'settings[comment][comment_article][settings][language][language_alterable]' => TRUE,
|
||||
'settings[comment][comment_article][translatable]' => TRUE,
|
||||
'settings[comment][comment_article][fields][comment_body]' => TRUE,
|
||||
// Override both comment subject fields to untranslatable.
|
||||
'settings[comment][comment_article][fields][subject]' => FALSE,
|
||||
'settings[comment][comment][fields][subject]' => FALSE,
|
||||
];
|
||||
$this->assertSettings('comment', 'comment_article', TRUE, $edit);
|
||||
$entity_field_manager = \Drupal::service('entity_field.manager');
|
||||
$definition = $entity_field_manager->getFieldDefinitions('comment', 'comment_article')['comment_body'];
|
||||
$this->assertTrue($definition->isTranslatable(), 'Article comment body is translatable.');
|
||||
$definition = $entity_field_manager->getFieldDefinitions('comment', 'comment_article')['subject'];
|
||||
$this->assertFalse($definition->isTranslatable(), 'Article comment subject is not translatable.');
|
||||
|
||||
$definition = $entity_field_manager->getFieldDefinitions('comment', 'comment')['comment_body'];
|
||||
$this->assertFalse($definition->isTranslatable(), 'Page comment body is not translatable.');
|
||||
$definition = $entity_field_manager->getFieldDefinitions('comment', 'comment')['subject'];
|
||||
$this->assertFalse($definition->isTranslatable(), 'Page comment subject is not translatable.');
|
||||
|
||||
// Test that translation can be enabled for base fields.
|
||||
$edit = [
|
||||
'entity_types[entity_test_mul]' => TRUE,
|
||||
'settings[entity_test_mul][entity_test_mul][translatable]' => TRUE,
|
||||
'settings[entity_test_mul][entity_test_mul][fields][name]' => TRUE,
|
||||
'settings[entity_test_mul][entity_test_mul][fields][user_id]' => FALSE,
|
||||
];
|
||||
$this->assertSettings('entity_test_mul', 'entity_test_mul', TRUE, $edit);
|
||||
$field_override = BaseFieldOverride::loadByName('entity_test_mul', 'entity_test_mul', 'name');
|
||||
$this->assertTrue($field_override->isTranslatable(), 'Base fields can be overridden with a base field bundle override entity.');
|
||||
$definitions = $entity_field_manager->getFieldDefinitions('entity_test_mul', 'entity_test_mul');
|
||||
$this->assertTrue($definitions['name']->isTranslatable());
|
||||
$this->assertFalse($definitions['user_id']->isTranslatable());
|
||||
|
||||
// Test that language settings are correctly stored.
|
||||
$language_configuration = ContentLanguageSettings::loadByEntityTypeBundle('comment', 'comment_article');
|
||||
$this->assertEquals('current_interface', $language_configuration->getDefaultLangcode(), 'The default language for article comments is set to the interface text language selected for page.');
|
||||
$this->assertTrue($language_configuration->isLanguageAlterable(), 'The language selector for article comments is shown.');
|
||||
|
||||
// Verify language widget appears on comment type form.
|
||||
$this->drupalGet('admin/structure/comment/manage/comment_article');
|
||||
$this->assertSession()->fieldExists('language_configuration[content_translation]');
|
||||
$this->assertSession()->checkboxChecked('edit-language-configuration-content-translation');
|
||||
|
||||
// Verify that translation may be enabled for the article content type.
|
||||
$edit = [
|
||||
'language_configuration[content_translation]' => TRUE,
|
||||
];
|
||||
// Make sure the checkbox is available and not checked by default.
|
||||
$this->drupalGet('admin/structure/types/manage/article');
|
||||
$this->assertSession()->fieldExists('language_configuration[content_translation]');
|
||||
$this->assertSession()->checkboxNotChecked('edit-language-configuration-content-translation');
|
||||
$this->drupalGet('admin/structure/types/manage/article');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->drupalGet('admin/structure/types/manage/article');
|
||||
$this->assertSession()->checkboxChecked('edit-language-configuration-content-translation');
|
||||
|
||||
// Test that the title field of nodes is available in the settings form.
|
||||
$edit = [
|
||||
'entity_types[node]' => TRUE,
|
||||
'settings[node][article][settings][language][langcode]' => 'current_interface',
|
||||
'settings[node][article][settings][language][language_alterable]' => TRUE,
|
||||
'settings[node][article][translatable]' => TRUE,
|
||||
'settings[node][article][fields][title]' => TRUE,
|
||||
];
|
||||
$this->assertSettings('node', 'article', TRUE, $edit);
|
||||
|
||||
foreach ([TRUE, FALSE] as $translatable) {
|
||||
// Test that configurable field translatability is correctly switched.
|
||||
$edit = ['settings[node][article][fields][body]' => $translatable];
|
||||
$this->assertSettings('node', 'article', TRUE, $edit);
|
||||
$field = FieldConfig::loadByName('node', 'article', 'body');
|
||||
$definitions = $entity_field_manager->getFieldDefinitions('node', 'article');
|
||||
$this->assertEquals($translatable, $definitions['body']->isTranslatable(), 'Field translatability correctly switched.');
|
||||
$this->assertEquals($definitions['body']->isTranslatable(), $field->isTranslatable(), 'Configurable field translatability correctly switched.');
|
||||
|
||||
// Test that also the Field UI form behaves correctly.
|
||||
$translatable = !$translatable;
|
||||
$edit = ['translatable' => $translatable];
|
||||
$this->drupalGet('admin/structure/types/manage/article/fields/node.article.body');
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
$entity_field_manager->clearCachedFieldDefinitions();
|
||||
$field = FieldConfig::loadByName('node', 'article', 'body');
|
||||
$definitions = $entity_field_manager->getFieldDefinitions('node', 'article');
|
||||
$this->assertEquals($translatable, $definitions['body']->isTranslatable(), 'Field translatability correctly switched.');
|
||||
$this->assertEquals($definitions['body']->isTranslatable(), $field->isTranslatable(), 'Configurable field translatability correctly switched.');
|
||||
}
|
||||
|
||||
// Test that we can't use the 'Not specified' default language when it is
|
||||
// not showing in the language selector.
|
||||
$edit = [
|
||||
'language_configuration[langcode]' => 'und',
|
||||
'language_configuration[language_alterable]' => FALSE,
|
||||
'language_configuration[content_translation]' => TRUE,
|
||||
];
|
||||
$this->drupalGet('admin/structure/types/manage/article');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->getSession()->getPage()->hasContent('"Show language selector" is not compatible with translating content that has default language: und. Either do not hide the language selector or pick a specific language.');
|
||||
|
||||
// Test that the order of the language list is similar to other language
|
||||
// lists, such as in Views UI.
|
||||
$this->drupalGet('admin/config/regional/content-language');
|
||||
|
||||
$expected_elements = [
|
||||
'site_default',
|
||||
'current_interface',
|
||||
'authors_default',
|
||||
'en',
|
||||
'und',
|
||||
'zxx',
|
||||
];
|
||||
$options = $this->assertSession()->selectExists('edit-settings-node-article-settings-language-langcode')->findAll('css', 'option');
|
||||
$options = array_map(function ($item) {
|
||||
return $item->getValue();
|
||||
}, $options);
|
||||
$this->assertSame($expected_elements, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the language settings checkbox on account settings page.
|
||||
*/
|
||||
public function testAccountLanguageSettingsUI(): void {
|
||||
// Make sure the checkbox is available and not checked by default.
|
||||
$this->drupalGet('admin/config/people/accounts');
|
||||
$this->assertSession()->fieldExists('language[content_translation]');
|
||||
$this->assertSession()->checkboxNotChecked('edit-language-content-translation');
|
||||
|
||||
$edit = [
|
||||
'language[content_translation]' => TRUE,
|
||||
];
|
||||
$this->drupalGet('admin/config/people/accounts');
|
||||
$this->submitForm($edit, 'Save configuration');
|
||||
$this->drupalGet('admin/config/people/accounts');
|
||||
$this->assertSession()->checkboxChecked('edit-language-content-translation');
|
||||
|
||||
// Make sure account settings can be saved.
|
||||
$this->drupalGet('admin/config/people/accounts');
|
||||
$this->submitForm(['anonymous' => 'Save me!'], 'Save configuration');
|
||||
$this->assertSession()->fieldValueEquals('anonymous', 'Save me!');
|
||||
$this->assertSession()->statusMessageContains('The configuration options have been saved.', 'status');
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that translatability has the expected value for the given bundle.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* The entity type for which to check translatability.
|
||||
* @param string|null $bundle
|
||||
* The bundle for which to check translatability.
|
||||
* @param bool $enabled
|
||||
* TRUE if translatability should be enabled, FALSE otherwise.
|
||||
* @param array $edit
|
||||
* An array of values to submit to the content translation settings page.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertSettings(string $entity_type, ?string $bundle, bool $enabled, array $edit): void {
|
||||
$this->drupalGet('admin/config/regional/content-language');
|
||||
$this->submitForm($edit, 'Save configuration');
|
||||
$status = $enabled ? 'enabled' : 'disabled';
|
||||
$message = "Translation for entity $entity_type ($bundle) is $status.";
|
||||
$this->assertEquals($enabled, \Drupal::service('content_translation.manager')->isEnabled($entity_type, $bundle), $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that field setting depends on bundle translatability.
|
||||
*/
|
||||
public function testFieldTranslatableSettingsUI(): void {
|
||||
// At least one field needs to be translatable to enable article for
|
||||
// translation. Create an extra field to be used for this purpose. We use
|
||||
// the UI to test our form alterations.
|
||||
$this->fieldUIAddNewField('admin/structure/types/manage/article', 'article_text', 'Test', 'text');
|
||||
|
||||
// Tests that field doesn't have translatable setting if bundle is not
|
||||
// translatable.
|
||||
$path = 'admin/structure/types/manage/article/fields/node.article.field_article_text';
|
||||
$this->drupalGet($path);
|
||||
$this->assertSession()->fieldDisabled('edit-translatable');
|
||||
$this->assertSession()->pageTextContains('To configure translation for this field, enable language support for this type.');
|
||||
|
||||
// 'Users may translate this field' should be unchecked by default.
|
||||
$this->assertSession()->checkboxNotChecked('translatable');
|
||||
|
||||
// Tests that field has translatable setting if bundle is translatable.
|
||||
// Note: this field is not translatable when enable bundle translatability.
|
||||
$edit = [
|
||||
'entity_types[node]' => TRUE,
|
||||
'settings[node][article][settings][language][language_alterable]' => TRUE,
|
||||
'settings[node][article][translatable]' => TRUE,
|
||||
'settings[node][article][fields][field_article_text]' => TRUE,
|
||||
];
|
||||
$this->assertSettings('node', 'article', TRUE, $edit);
|
||||
$this->drupalGet($path);
|
||||
$this->assertSession()->fieldEnabled('edit-translatable');
|
||||
$this->assertSession()->checkboxChecked('edit-translatable');
|
||||
$this->assertSession()->pageTextNotContains('To enable translation of this field, enable language support for this type.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the translatable settings checkbox for untranslatable entities.
|
||||
*/
|
||||
public function testNonTranslatableTranslationSettingsUI(): void {
|
||||
$this->drupalGet('admin/config/regional/content-language');
|
||||
$this->assertSession()->fieldNotExists('settings[entity_test][entity_test][translatable]');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Functional;
|
||||
|
||||
use Drupal\block_content\Entity\BlockContentType;
|
||||
use Drupal\comment\Entity\CommentType;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the Content translation settings.
|
||||
*
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationStandardFieldsTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'language',
|
||||
'content_translation',
|
||||
'node',
|
||||
'comment',
|
||||
'field_ui',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $profile = 'testing';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'access administration pages',
|
||||
'administer languages',
|
||||
'administer content translation',
|
||||
'administer content types',
|
||||
'administer node fields',
|
||||
'administer comment fields',
|
||||
'administer comments',
|
||||
'administer comment types',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that translatable fields are being rendered.
|
||||
*/
|
||||
public function testFieldTranslatableArticle(): void {
|
||||
// Install block and field modules.
|
||||
\Drupal::service('module_installer')->install(
|
||||
[
|
||||
'block',
|
||||
'block_content',
|
||||
'filter',
|
||||
'image',
|
||||
'text',
|
||||
]);
|
||||
|
||||
// Create a basic block type with a body field.
|
||||
$bundle = BlockContentType::create([
|
||||
'id' => 'basic',
|
||||
'label' => 'Basic',
|
||||
'revision' => FALSE,
|
||||
]);
|
||||
$bundle->save();
|
||||
block_content_add_body_field($bundle->id());
|
||||
|
||||
// Create a comment type with a body field.
|
||||
$bundle = CommentType::create([
|
||||
'id' => 'comment',
|
||||
'label' => 'Comment',
|
||||
'target_entity_type_id' => 'node',
|
||||
]);
|
||||
$bundle->save();
|
||||
\Drupal::service('comment.manager')->addBodyField('comment');
|
||||
|
||||
// Create the article content type and add a comment, image and tag field.
|
||||
$this->drupalCreateContentType(['type' => 'article', 'title' => 'Article']);
|
||||
|
||||
$entity_type_manager = \Drupal::entityTypeManager();
|
||||
$entity_type_manager->getStorage('field_storage_config')->create([
|
||||
'entity_type' => 'node',
|
||||
'field_name' => 'comment',
|
||||
'type' => 'text',
|
||||
])->save();
|
||||
|
||||
$entity_type_manager->getStorage('field_config')->create([
|
||||
'label' => 'Comments',
|
||||
'field_name' => 'comment',
|
||||
'entity_type' => 'node',
|
||||
'bundle' => 'article',
|
||||
])->save();
|
||||
|
||||
$entity_type_manager->getStorage('field_storage_config')->create([
|
||||
'entity_type' => 'node',
|
||||
'field_name' => 'field_image',
|
||||
'type' => 'image',
|
||||
])->save();
|
||||
|
||||
$entity_type_manager->getStorage('field_config')->create([
|
||||
'label' => 'Image',
|
||||
'field_name' => 'field_image',
|
||||
'entity_type' => 'node',
|
||||
'bundle' => 'article',
|
||||
])->save();
|
||||
|
||||
$entity_type_manager->getStorage('field_storage_config')->create([
|
||||
'entity_type' => 'node',
|
||||
'field_name' => 'field_tags',
|
||||
'type' => 'text',
|
||||
])->save();
|
||||
|
||||
$entity_type_manager->getStorage('field_config')->create([
|
||||
'label' => 'Tags',
|
||||
'field_name' => 'field_tags',
|
||||
'entity_type' => 'node',
|
||||
'bundle' => 'article',
|
||||
])->save();
|
||||
|
||||
$entity_type_manager->getStorage('field_storage_config')->create([
|
||||
'entity_type' => 'user',
|
||||
'field_name' => 'user_picture',
|
||||
'type' => 'image',
|
||||
])->save();
|
||||
|
||||
// Add a user picture field to the user entity.
|
||||
$entity_type_manager->getStorage('field_config')->create([
|
||||
'label' => 'Tags',
|
||||
'field_name' => 'user_picture',
|
||||
'entity_type' => 'user',
|
||||
'bundle' => 'user',
|
||||
])->save();
|
||||
|
||||
$path = 'admin/config/regional/content-language';
|
||||
$this->drupalGet($path);
|
||||
|
||||
// Check content block fields.
|
||||
$this->assertSession()->checkboxChecked('edit-settings-block-content-basic-fields-body');
|
||||
|
||||
// Check comment fields.
|
||||
$this->assertSession()->checkboxChecked('edit-settings-comment-comment-fields-comment-body');
|
||||
|
||||
// Check node fields.
|
||||
$this->assertSession()->checkboxChecked('edit-settings-node-article-fields-comment');
|
||||
$this->assertSession()->checkboxChecked('edit-settings-node-article-fields-field-image');
|
||||
$this->assertSession()->checkboxChecked('edit-settings-node-article-fields-field-tags');
|
||||
|
||||
// Check user fields.
|
||||
$this->assertSession()->checkboxChecked('edit-settings-user-user-fields-user-picture');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that revision_log is not translatable.
|
||||
*/
|
||||
public function testRevisionLogNotTranslatable(): void {
|
||||
$path = 'admin/config/regional/content-language';
|
||||
$this->drupalGet($path);
|
||||
$this->assertSession()->fieldNotExists('edit-settings-node-article-fields-revision-log');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,271 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Functional;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\Tests\TestFileCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests the field synchronization behavior for the image field.
|
||||
*
|
||||
* @covers ::_content_translation_form_language_content_settings_form_alter
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationSyncImageTest extends ContentTranslationTestBase {
|
||||
|
||||
use TestFileCreationTrait {
|
||||
getTestFiles as drupalGetTestFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The cardinality of the image field.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $cardinality;
|
||||
|
||||
/**
|
||||
* The test image files.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $files;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'language',
|
||||
'content_translation',
|
||||
'entity_test',
|
||||
'image',
|
||||
'field_ui',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->doSetup();
|
||||
$this->files = $this->drupalGetTestFiles('image');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the test image field.
|
||||
*/
|
||||
protected function setupTestFields(): void {
|
||||
$this->fieldName = 'field_test_et_ui_image';
|
||||
$this->cardinality = 3;
|
||||
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => $this->fieldName,
|
||||
'entity_type' => $this->entityTypeId,
|
||||
'type' => 'image',
|
||||
'cardinality' => $this->cardinality,
|
||||
])->save();
|
||||
|
||||
FieldConfig::create([
|
||||
'entity_type' => $this->entityTypeId,
|
||||
'field_name' => $this->fieldName,
|
||||
'bundle' => $this->entityTypeId,
|
||||
'label' => 'Test translatable image field',
|
||||
'third_party_settings' => [
|
||||
'content_translation' => [
|
||||
'translation_sync' => [
|
||||
'file' => FALSE,
|
||||
'alt' => 'alt',
|
||||
'title' => 'title',
|
||||
],
|
||||
],
|
||||
],
|
||||
])->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEditorPermissions(): array {
|
||||
// Every entity-type-specific test needs to define these.
|
||||
return ['administer entity_test_mul fields', 'administer languages', 'administer content translation'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests image field synchronization.
|
||||
*/
|
||||
public function testImageFieldSync(): void {
|
||||
// Check that the alt and title fields are enabled for the image field.
|
||||
$this->drupalLogin($this->editor);
|
||||
$this->drupalGet('entity_test_mul/structure/' . $this->entityTypeId . '/fields/' . $this->entityTypeId . '.' . $this->entityTypeId . '.' . $this->fieldName);
|
||||
$this->assertSession()->checkboxChecked('edit-third-party-settings-content-translation-translation-sync-alt');
|
||||
$this->assertSession()->checkboxChecked('edit-third-party-settings-content-translation-translation-sync-title');
|
||||
$edit = [
|
||||
'third_party_settings[content_translation][translation_sync][alt]' => FALSE,
|
||||
'third_party_settings[content_translation][translation_sync][title]' => FALSE,
|
||||
];
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
|
||||
// Check that the content translation settings page reflects the changes
|
||||
// performed in the field edit page.
|
||||
$this->drupalGet('admin/config/regional/content-language');
|
||||
$this->assertSession()->checkboxNotChecked('edit-settings-entity-test-mul-entity-test-mul-columns-field-test-et-ui-image-alt');
|
||||
$this->assertSession()->checkboxNotChecked('edit-settings-entity-test-mul-entity-test-mul-columns-field-test-et-ui-image-title');
|
||||
$edit = [
|
||||
'settings[entity_test_mul][entity_test_mul][fields][field_test_et_ui_image]' => TRUE,
|
||||
'settings[entity_test_mul][entity_test_mul][columns][field_test_et_ui_image][alt]' => TRUE,
|
||||
'settings[entity_test_mul][entity_test_mul][columns][field_test_et_ui_image][title]' => TRUE,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/content-language');
|
||||
$this->submitForm($edit, 'Save configuration');
|
||||
$this->assertSession()->statusMessageNotExists('error');
|
||||
$this->assertSession()->checkboxChecked('edit-settings-entity-test-mul-entity-test-mul-columns-field-test-et-ui-image-alt');
|
||||
$this->assertSession()->checkboxChecked('edit-settings-entity-test-mul-entity-test-mul-columns-field-test-et-ui-image-title');
|
||||
$this->drupalLogin($this->translator);
|
||||
|
||||
$default_langcode = $this->langcodes[0];
|
||||
$langcode = $this->langcodes[1];
|
||||
|
||||
// Populate the test entity with some random initial values.
|
||||
$values = [
|
||||
'name' => $this->randomMachineName(),
|
||||
'user_id' => 2,
|
||||
'langcode' => $default_langcode,
|
||||
];
|
||||
$entity = \Drupal::entityTypeManager()
|
||||
->getStorage($this->entityTypeId)
|
||||
->create($values);
|
||||
|
||||
// Create some file entities from the generated test files and store them.
|
||||
$values = [];
|
||||
for ($delta = 0; $delta < $this->cardinality; $delta++) {
|
||||
// For the default language use the same order for files and field items.
|
||||
$index = $delta;
|
||||
|
||||
// Create the file entity for the image being processed and record its
|
||||
// identifier.
|
||||
$field_values = [
|
||||
'uri' => $this->files[$index]->uri,
|
||||
'uid' => \Drupal::currentUser()->id(),
|
||||
];
|
||||
$file = File::create($field_values);
|
||||
$file->setPermanent();
|
||||
$file->save();
|
||||
$fid = $file->id();
|
||||
$this->files[$index]->fid = $fid;
|
||||
|
||||
// Generate the item for the current image file entity and attach it to
|
||||
// the entity.
|
||||
$item = [
|
||||
'target_id' => $fid,
|
||||
'alt' => $default_langcode . '_' . $fid . '_' . $this->randomMachineName(),
|
||||
'title' => $default_langcode . '_' . $fid . '_' . $this->randomMachineName(),
|
||||
];
|
||||
$entity->{$this->fieldName}[] = $item;
|
||||
|
||||
// Store the generated values keying them by fid for easier lookup.
|
||||
$values[$default_langcode][$fid] = $item;
|
||||
}
|
||||
$entity = $this->saveEntity($entity);
|
||||
|
||||
// Create some field translations for the test image field. The translated
|
||||
// items will be one less than the original values to check that only the
|
||||
// translated ones will be preserved. In fact we want the same fids and
|
||||
// items order for both languages.
|
||||
$translation = $entity->addTranslation($langcode);
|
||||
for ($delta = 0; $delta < $this->cardinality - 1; $delta++) {
|
||||
// Simulate a field reordering: items are shifted of one position ahead.
|
||||
// The modulo operator ensures we start from the beginning after reaching
|
||||
// the maximum allowed delta.
|
||||
$index = ($delta + 1) % $this->cardinality;
|
||||
|
||||
// Generate the item for the current image file entity and attach it to
|
||||
// the entity.
|
||||
$fid = $this->files[$index]->fid;
|
||||
$item = [
|
||||
'target_id' => $fid,
|
||||
'alt' => $langcode . '_' . $fid . '_' . $this->randomMachineName(),
|
||||
'title' => $langcode . '_' . $fid . '_' . $this->randomMachineName(),
|
||||
];
|
||||
$translation->{$this->fieldName}[] = $item;
|
||||
|
||||
// Again store the generated values keying them by fid for easier lookup.
|
||||
$values[$langcode][$fid] = $item;
|
||||
}
|
||||
|
||||
// Perform synchronization: the translation language is used as source,
|
||||
// while the default language is used as target.
|
||||
$this->manager->getTranslationMetadata($translation)->setSource($default_langcode);
|
||||
$entity = $this->saveEntity($translation);
|
||||
$translation = $entity->getTranslation($langcode);
|
||||
|
||||
// Check that one value has been dropped from the original values.
|
||||
$assert = count($entity->{$this->fieldName}) == 2;
|
||||
$this->assertTrue($assert, 'One item correctly removed from the synchronized field values.');
|
||||
|
||||
// Check that fids have been synchronized and translatable column values
|
||||
// have been retained.
|
||||
$fids = [];
|
||||
foreach ($entity->{$this->fieldName} as $delta => $item) {
|
||||
$value = $values[$default_langcode][$item->target_id];
|
||||
$source_item = $translation->{$this->fieldName}->get($delta);
|
||||
$assert = $item->target_id == $source_item->target_id && $item->alt == $value['alt'] && $item->title == $value['title'];
|
||||
$this->assertTrue($assert, "Field item $item->target_id has been successfully synchronized.");
|
||||
$fids[$item->target_id] = TRUE;
|
||||
}
|
||||
|
||||
// Check that the dropped value is the right one.
|
||||
$removed_fid = $this->files[0]->fid;
|
||||
$this->assertTrue(!isset($fids[$removed_fid]), "Field item $removed_fid has been correctly removed.");
|
||||
|
||||
// Add back an item for the dropped value and perform synchronization again.
|
||||
$values[$langcode][$removed_fid] = [
|
||||
'target_id' => $removed_fid,
|
||||
'alt' => $langcode . '_' . $removed_fid . '_' . $this->randomMachineName(),
|
||||
'title' => $langcode . '_' . $removed_fid . '_' . $this->randomMachineName(),
|
||||
];
|
||||
$translation->{$this->fieldName}->setValue(array_values($values[$langcode]));
|
||||
$entity = $this->saveEntity($translation);
|
||||
$translation = $entity->getTranslation($langcode);
|
||||
|
||||
// Check that the value has been added to the default language.
|
||||
$assert = count($entity->{$this->fieldName}->getValue()) == 3;
|
||||
$this->assertTrue($assert, 'One item correctly added to the synchronized field values.');
|
||||
|
||||
foreach ($entity->{$this->fieldName} as $delta => $item) {
|
||||
// When adding an item its value is copied over all the target languages,
|
||||
// thus in this case the source language needs to be used to check the
|
||||
// values instead of the target one.
|
||||
$fid_langcode = $item->target_id != $removed_fid ? $default_langcode : $langcode;
|
||||
$value = $values[$fid_langcode][$item->target_id];
|
||||
$source_item = $translation->{$this->fieldName}->get($delta);
|
||||
$assert = $item->target_id == $source_item->target_id && $item->alt == $value['alt'] && $item->title == $value['title'];
|
||||
$this->assertTrue($assert, "Field item $item->target_id has been successfully synchronized.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the passed entity and reloads it, enabling compatibility mode.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity to be saved.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface
|
||||
* The saved entity.
|
||||
*/
|
||||
protected function saveEntity(EntityInterface $entity): EntityInterface {
|
||||
$entity->save();
|
||||
$entity = \Drupal::entityTypeManager()->getStorage('entity_test_mul')->loadUnchanged($entity->id());
|
||||
return $entity;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,259 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Functional;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Base class for content translation tests.
|
||||
*/
|
||||
abstract class ContentTranslationTestBase extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['text'];
|
||||
|
||||
/**
|
||||
* The entity type being tested.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $entityTypeId = 'entity_test_mul';
|
||||
|
||||
/**
|
||||
* The bundle being tested.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $bundle;
|
||||
|
||||
/**
|
||||
* The added languages.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $langcodes;
|
||||
|
||||
/**
|
||||
* The account to be used to test translation operations.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $translator;
|
||||
|
||||
/**
|
||||
* The account to be used to test multilingual entity editing.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $editor;
|
||||
|
||||
/**
|
||||
* The account to be used to test access to both workflows.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $administrator;
|
||||
|
||||
/**
|
||||
* The name of the field used to test translation.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName;
|
||||
|
||||
/**
|
||||
* The translation controller for the current entity type.
|
||||
*
|
||||
* @var \Drupal\content_translation\ContentTranslationHandlerInterface
|
||||
*/
|
||||
protected $controller;
|
||||
|
||||
/**
|
||||
* @var \Drupal\content_translation\ContentTranslationManagerInterface
|
||||
*/
|
||||
protected $manager;
|
||||
|
||||
/**
|
||||
* Completes preparation for content translation tests.
|
||||
*/
|
||||
protected function doSetup(): void {
|
||||
$this->setupLanguages();
|
||||
$this->setupBundle();
|
||||
$this->enableTranslation();
|
||||
$this->setupUsers();
|
||||
$this->setupTestFields();
|
||||
|
||||
$this->manager = $this->container->get('content_translation.manager');
|
||||
$this->controller = $this->manager->getTranslationHandler($this->entityTypeId);
|
||||
|
||||
// Rebuild the container so that the new languages are picked up by services
|
||||
// that hold a list of languages.
|
||||
$this->rebuildContainer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds additional languages.
|
||||
*/
|
||||
protected function setupLanguages() {
|
||||
$this->langcodes = ['it', 'fr'];
|
||||
foreach ($this->langcodes as $langcode) {
|
||||
ConfigurableLanguage::createFromLangcode($langcode)->save();
|
||||
}
|
||||
array_unshift($this->langcodes, \Drupal::languageManager()->getDefaultLanguage()->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of permissions needed for the translator.
|
||||
*/
|
||||
protected function getTranslatorPermissions() {
|
||||
return array_filter([$this->getTranslatePermission(), 'create content translations', 'update content translations', 'delete content translations']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the translate permissions for the current entity and bundle.
|
||||
*/
|
||||
protected function getTranslatePermission() {
|
||||
$entity_type = \Drupal::entityTypeManager()->getDefinition($this->entityTypeId);
|
||||
if ($permission_granularity = $entity_type->getPermissionGranularity()) {
|
||||
return $permission_granularity == 'bundle' ? "translate {$this->bundle} {$this->entityTypeId}" : "translate {$this->entityTypeId}";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of permissions needed for the editor.
|
||||
*/
|
||||
protected function getEditorPermissions() {
|
||||
// Every entity-type-specific test needs to define these.
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of permissions needed for the administrator.
|
||||
*/
|
||||
protected function getAdministratorPermissions() {
|
||||
return array_merge($this->getEditorPermissions(), $this->getTranslatorPermissions(), ['administer languages', 'administer content translation']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and activates translator, editor and admin users.
|
||||
*/
|
||||
protected function setupUsers() {
|
||||
$this->translator = $this->drupalCreateUser($this->getTranslatorPermissions(), 'translator');
|
||||
$this->editor = $this->drupalCreateUser($this->getEditorPermissions(), 'editor');
|
||||
$this->administrator = $this->drupalCreateUser($this->getAdministratorPermissions(), 'administrator');
|
||||
$this->drupalLogin($this->translator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates or initializes the bundle date if needed.
|
||||
*/
|
||||
protected function setupBundle() {
|
||||
if (empty($this->bundle)) {
|
||||
$this->bundle = $this->entityTypeId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables translation for the current entity type and bundle.
|
||||
*/
|
||||
protected function enableTranslation() {
|
||||
// Enable translation for the current entity type and ensure the change is
|
||||
// picked up.
|
||||
\Drupal::service('content_translation.manager')->setEnabled($this->entityTypeId, $this->bundle, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the test fields.
|
||||
*/
|
||||
protected function setupTestFields() {
|
||||
if (empty($this->fieldName)) {
|
||||
$this->fieldName = 'field_test_et_ui_test';
|
||||
}
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => $this->fieldName,
|
||||
'type' => 'string',
|
||||
'entity_type' => $this->entityTypeId,
|
||||
'cardinality' => 1,
|
||||
])->save();
|
||||
FieldConfig::create([
|
||||
'entity_type' => $this->entityTypeId,
|
||||
'field_name' => $this->fieldName,
|
||||
'bundle' => $this->bundle,
|
||||
'label' => 'Test translatable text-field',
|
||||
])->save();
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
$display_repository->getFormDisplay($this->entityTypeId, $this->bundle, 'default')
|
||||
->setComponent($this->fieldName, [
|
||||
'type' => 'string_textfield',
|
||||
'weight' => 0,
|
||||
])
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the entity to be translated.
|
||||
*
|
||||
* @param array $values
|
||||
* An array of initial values for the entity.
|
||||
* @param string $langcode
|
||||
* The initial language code of the entity.
|
||||
* @param string $bundle_name
|
||||
* (optional) The entity bundle, if the entity uses bundles. Defaults to
|
||||
* NULL. If left NULL, $this->bundle will be used.
|
||||
*
|
||||
* @return string
|
||||
* The entity id.
|
||||
*/
|
||||
protected function createEntity($values, $langcode, $bundle_name = NULL) {
|
||||
$entity_values = $values;
|
||||
$entity_values['langcode'] = $langcode;
|
||||
$entity_type = \Drupal::entityTypeManager()->getDefinition($this->entityTypeId);
|
||||
if ($bundle_key = $entity_type->getKey('bundle')) {
|
||||
$entity_values[$bundle_key] = $bundle_name ?: $this->bundle;
|
||||
}
|
||||
$storage = $this->container->get('entity_type.manager')->getStorage($this->entityTypeId);
|
||||
if (!($storage instanceof SqlContentEntityStorage)) {
|
||||
foreach ($values as $property => $value) {
|
||||
if (is_array($value)) {
|
||||
$entity_values[$property] = [$langcode => $value];
|
||||
}
|
||||
}
|
||||
}
|
||||
$entity = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityTypeId)
|
||||
->create($entity_values);
|
||||
$entity->save();
|
||||
return $entity->id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the edit URL for the specified entity.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
|
||||
* The entity being edited.
|
||||
*
|
||||
* @return \Drupal\Core\Url
|
||||
* The edit URL.
|
||||
*/
|
||||
protected function getEditUrl(ContentEntityInterface $entity) {
|
||||
if ($entity->access('update', $this->loggedInUser)) {
|
||||
$url = $entity->toUrl('edit-form');
|
||||
}
|
||||
else {
|
||||
$url = $entity->toUrl('drupal:content-translation-edit');
|
||||
$url->setRouteParameter('language', $entity->language()->getId());
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the content translation UI check skip.
|
||||
*
|
||||
* @covers \Drupal\language\Form\ContentLanguageSettingsForm
|
||||
* @covers ::_content_translation_form_language_content_settings_form_alter
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationUISkipTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['content_translation_test', 'user', 'node'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests the content_translation_ui_skip key functionality.
|
||||
*/
|
||||
public function testUICheckSkip(): void {
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'translate any entity',
|
||||
'administer content translation',
|
||||
'administer languages',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
// Visit the content translation.
|
||||
$this->drupalGet('admin/config/regional/content-language');
|
||||
|
||||
// Check the message regarding UI integration.
|
||||
$this->assertSession()->pageTextContains('Test entity - Translatable skip UI check');
|
||||
$this->assertSession()->pageTextContains('Test entity - Translatable check UI (Translation is not supported)');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,633 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Functional;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Entity\EntityChangedInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Language\Language;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait;
|
||||
use Drupal\user\UserInterface;
|
||||
|
||||
/**
|
||||
* Tests the Content Translation UI.
|
||||
*/
|
||||
abstract class ContentTranslationUITestBase extends ContentTranslationTestBase {
|
||||
|
||||
use AssertPageCacheContextsAndTagsTrait;
|
||||
|
||||
/**
|
||||
* The id of the entity being translated.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $entityId;
|
||||
|
||||
/**
|
||||
* Whether the behavior of the language selector should be tested.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $testLanguageSelector = TRUE;
|
||||
|
||||
/**
|
||||
* Flag to determine if "all languages" rendering is tested.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $testHTMLEscapeForAllLanguages = FALSE;
|
||||
|
||||
/**
|
||||
* Default cache contexts expected on a non-translated entity.
|
||||
*
|
||||
* Cache contexts will not be checked if this list is empty.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $defaultCacheContexts = ['languages:language_interface', 'theme', 'url.query_args:_wrapper_format', 'user.permissions'];
|
||||
|
||||
/**
|
||||
* Tests the basic translation UI.
|
||||
*/
|
||||
public function testTranslationUI(): void {
|
||||
$this->doTestBasicTranslation();
|
||||
$this->doTestTranslationOverview();
|
||||
$this->doTestOutdatedStatus();
|
||||
$this->doTestPublishedStatus();
|
||||
$this->doTestAuthoringInfo();
|
||||
$this->doTestTranslationEdit();
|
||||
$this->doTestTranslationChanged();
|
||||
$this->doTestChangedTimeAfterSaveWithoutChanges();
|
||||
$this->doTestTranslationDeletion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the basic translation workflow.
|
||||
*/
|
||||
protected function doTestBasicTranslation() {
|
||||
// Create a new test entity with original values in the default language.
|
||||
$default_langcode = $this->langcodes[0];
|
||||
$values[$default_langcode] = $this->getNewEntityValues($default_langcode);
|
||||
// Create the entity with the editor as owner, so that afterwards a new
|
||||
// translation is created by the translator and the translation author is
|
||||
// tested.
|
||||
$this->drupalLogin($this->editor);
|
||||
$this->entityId = $this->createEntity($values[$default_langcode], $default_langcode);
|
||||
$this->drupalLogin($this->translator);
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityTypeId);
|
||||
$entity = $storage->load($this->entityId);
|
||||
$this->assertNotEmpty($entity, 'Entity found in the database.');
|
||||
$this->drupalGet($entity->toUrl());
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Ensure that the content language cache context is not yet added to the
|
||||
// page.
|
||||
$this->assertCacheContexts($this->defaultCacheContexts);
|
||||
|
||||
$this->drupalGet($entity->toUrl('drupal:content-translation-overview'));
|
||||
$this->assertSession()->pageTextNotContains('Source language');
|
||||
|
||||
$translation = $this->getTranslation($entity, $default_langcode);
|
||||
foreach ($values[$default_langcode] as $property => $value) {
|
||||
$stored_value = $this->getValue($translation, $property, $default_langcode);
|
||||
$value = is_array($value) ? $value[0]['value'] : $value;
|
||||
$message = "$property correctly stored in the default language.";
|
||||
$this->assertEquals($value, $stored_value, $message);
|
||||
}
|
||||
|
||||
// Add a content translation.
|
||||
$langcode = 'it';
|
||||
$language = ConfigurableLanguage::load($langcode);
|
||||
$values[$langcode] = $this->getNewEntityValues($langcode);
|
||||
|
||||
$entity_type_id = $entity->getEntityTypeId();
|
||||
$add_url = Url::fromRoute("entity.$entity_type_id.content_translation_add", [
|
||||
$entity->getEntityTypeId() => $entity->id(),
|
||||
'source' => $default_langcode,
|
||||
'target' => $langcode,
|
||||
], ['language' => $language]);
|
||||
$this->drupalGet($add_url);
|
||||
$this->submitForm($this->getEditValues($values, $langcode), $this->getFormSubmitActionForNewTranslation($entity, $langcode));
|
||||
|
||||
// Assert that HTML is not escaped unexpectedly.
|
||||
if ($this->testHTMLEscapeForAllLanguages) {
|
||||
$this->assertSession()->responseNotContains('<span class="translation-entity-all-languages">(all languages)</span>');
|
||||
$this->assertSession()->responseContains('<span class="translation-entity-all-languages">(all languages)</span>');
|
||||
}
|
||||
|
||||
// Ensure that the content language cache context is not yet added to the
|
||||
// page.
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityTypeId);
|
||||
$entity = $storage->load($this->entityId);
|
||||
$this->drupalGet($entity->toUrl());
|
||||
$this->assertCacheContexts(Cache::mergeContexts(['languages:language_content'], $this->defaultCacheContexts));
|
||||
|
||||
// Reset the cache of the entity, so that the new translation gets the
|
||||
// updated values.
|
||||
$metadata_source_translation = $this->manager->getTranslationMetadata($entity->getTranslation($default_langcode));
|
||||
$metadata_target_translation = $this->manager->getTranslationMetadata($entity->getTranslation($langcode));
|
||||
|
||||
$author_field_name = $entity->hasField('content_translation_uid') ? 'content_translation_uid' : 'uid';
|
||||
if ($entity->getFieldDefinition($author_field_name)->isTranslatable()) {
|
||||
$this->assertEquals($this->translator->id(), $metadata_target_translation->getAuthor()->id(), "Author of the target translation $langcode correctly stored for translatable owner field.");
|
||||
|
||||
$this->assertNotEquals($metadata_target_translation->getAuthor()->id(), $metadata_source_translation->getAuthor()->id(),
|
||||
"Author of the target translation $langcode different from the author of the source translation $default_langcode for translatable owner field.");
|
||||
}
|
||||
else {
|
||||
$this->assertEquals($this->editor->id(), $metadata_target_translation->getAuthor()->id(), 'Author of the entity remained untouched after translation for non translatable owner field.');
|
||||
}
|
||||
|
||||
$created_field_name = $entity->hasField('content_translation_created') ? 'content_translation_created' : 'created';
|
||||
if ($entity->getFieldDefinition($created_field_name)->isTranslatable()) {
|
||||
// Verify that the translation creation timestamp of the target
|
||||
// translation language is newer than the creation timestamp of the source
|
||||
// translation default language for the translatable created field.
|
||||
$this->assertGreaterThan($metadata_source_translation->getCreatedTime(), $metadata_target_translation->getCreatedTime());
|
||||
}
|
||||
else {
|
||||
$this->assertEquals($metadata_source_translation->getCreatedTime(), $metadata_target_translation->getCreatedTime(), 'Creation timestamp of the entity remained untouched after translation for non translatable created field.');
|
||||
}
|
||||
|
||||
if ($this->testLanguageSelector) {
|
||||
// Verify that language selector is correctly disabled on translations.
|
||||
$this->assertSession()->fieldNotExists('edit-langcode-0-value');
|
||||
}
|
||||
$entity = $storage->load($this->entityId);
|
||||
$this->drupalGet($entity->toUrl('drupal:content-translation-overview'));
|
||||
$this->assertSession()->pageTextNotContains('Source language');
|
||||
|
||||
// Switch the source language.
|
||||
$langcode = 'fr';
|
||||
$language = ConfigurableLanguage::load($langcode);
|
||||
$source_langcode = 'it';
|
||||
$edit = ['source_langcode[source]' => $source_langcode];
|
||||
$entity_type_id = $entity->getEntityTypeId();
|
||||
$add_url = Url::fromRoute("entity.$entity_type_id.content_translation_add", [
|
||||
$entity->getEntityTypeId() => $entity->id(),
|
||||
'source' => $default_langcode,
|
||||
'target' => $langcode,
|
||||
], ['language' => $language]);
|
||||
// This does not save anything, it merely reloads the form and fills in the
|
||||
// fields with the values from the different source language.
|
||||
$this->drupalGet($add_url);
|
||||
$this->submitForm($edit, 'Change');
|
||||
$this->assertSession()->fieldValueEquals("{$this->fieldName}[0][value]", $values[$source_langcode][$this->fieldName][0]['value']);
|
||||
|
||||
// Add another translation and mark the other ones as outdated.
|
||||
$values[$langcode] = $this->getNewEntityValues($langcode);
|
||||
$edit = $this->getEditValues($values, $langcode) + ['content_translation[retranslate]' => TRUE];
|
||||
$entity_type_id = $entity->getEntityTypeId();
|
||||
$add_url = Url::fromRoute("entity.$entity_type_id.content_translation_add", [
|
||||
$entity->getEntityTypeId() => $entity->id(),
|
||||
'source' => $source_langcode,
|
||||
'target' => $langcode,
|
||||
], ['language' => $language]);
|
||||
$this->drupalGet($add_url);
|
||||
$this->submitForm($edit, $this->getFormSubmitActionForNewTranslation($entity, $langcode));
|
||||
$entity = $storage->load($this->entityId);
|
||||
$this->drupalGet($entity->toUrl('drupal:content-translation-overview'));
|
||||
$this->assertSession()->pageTextContains('Source language');
|
||||
|
||||
// Check that the entered values have been correctly stored.
|
||||
foreach ($values as $langcode => $property_values) {
|
||||
$translation = $this->getTranslation($entity, $langcode);
|
||||
foreach ($property_values as $property => $value) {
|
||||
$stored_value = $this->getValue($translation, $property, $langcode);
|
||||
$value = is_array($value) ? $value[0]['value'] : $value;
|
||||
$message = "$property correctly stored with language $langcode.";
|
||||
$this->assertEquals($value, $stored_value, $message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the translation overview shows the correct values.
|
||||
*/
|
||||
protected function doTestTranslationOverview() {
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityTypeId);
|
||||
$entity = $storage->load($this->entityId);
|
||||
$translate_url = $entity->toUrl('drupal:content-translation-overview');
|
||||
$this->drupalGet($translate_url);
|
||||
$translate_url->setAbsolute(FALSE);
|
||||
|
||||
foreach ($this->langcodes as $langcode) {
|
||||
if ($entity->hasTranslation($langcode)) {
|
||||
$language = new Language(['id' => $langcode]);
|
||||
// Test that label is correctly shown for translation.
|
||||
$view_url = $entity->toUrl('canonical', ['language' => $language])->toString();
|
||||
$this->assertSession()->elementTextEquals('xpath', "//table//a[@href='{$view_url}']", $entity->getTranslation($langcode)->label() ?? $entity->getTranslation($langcode)->id());
|
||||
// Test that edit link is correct for translation.
|
||||
$edit_path = $entity->toUrl('edit-form', ['language' => $language])->toString();
|
||||
$this->assertSession()->elementTextEquals('xpath', "//table//ul[@class='dropbutton']/li/a[@href='{$edit_path}']", 'Edit');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests up-to-date status tracking.
|
||||
*/
|
||||
protected function doTestOutdatedStatus() {
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityTypeId);
|
||||
$entity = $storage->load($this->entityId);
|
||||
$langcode = 'fr';
|
||||
$languages = \Drupal::languageManager()->getLanguages();
|
||||
|
||||
// Mark translations as outdated.
|
||||
$edit = ['content_translation[retranslate]' => TRUE];
|
||||
$edit_path = $entity->toUrl('edit-form', ['language' => $languages[$langcode]]);
|
||||
$this->drupalGet($edit_path);
|
||||
$this->submitForm($edit, $this->getFormSubmitAction($entity, $langcode));
|
||||
$entity = $storage->load($this->entityId);
|
||||
|
||||
// Check that every translation has the correct "outdated" status, and that
|
||||
// the Translation fieldset is open if the translation is "outdated".
|
||||
foreach ($this->langcodes as $added_langcode) {
|
||||
$url = $entity->toUrl('edit-form', ['language' => ConfigurableLanguage::load($added_langcode)]);
|
||||
$this->drupalGet($url);
|
||||
if ($added_langcode == $langcode) {
|
||||
// Verify that the retranslate flag is not checked by default.
|
||||
$this->assertSession()->fieldValueEquals('content_translation[retranslate]', FALSE);
|
||||
$this->assertSession()->elementNotExists('xpath', '//details[@id="edit-content-translation" and @open="open"]');
|
||||
}
|
||||
else {
|
||||
// Verify that the translate flag is checked by default.
|
||||
$this->assertSession()->fieldValueEquals('content_translation[outdated]', TRUE);
|
||||
$this->assertSession()->elementExists('xpath', '//details[@id="edit-content-translation" and @open="open"]');
|
||||
$edit = ['content_translation[outdated]' => FALSE];
|
||||
$this->drupalGet($url);
|
||||
$this->submitForm($edit, $this->getFormSubmitAction($entity, $added_langcode));
|
||||
$this->drupalGet($url);
|
||||
// Verify that retranslate flag is now shown.
|
||||
$this->assertSession()->fieldValueEquals('content_translation[retranslate]', FALSE);
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityTypeId);
|
||||
$storage->resetCache([$this->entityId]);
|
||||
$entity = $storage->load($this->entityId);
|
||||
$this->assertFalse($this->manager->getTranslationMetadata($entity->getTranslation($added_langcode))->isOutdated(), 'The "outdated" status has been correctly stored.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the translation publishing status.
|
||||
*/
|
||||
protected function doTestPublishedStatus() {
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityTypeId);
|
||||
$entity = $storage->load($this->entityId);
|
||||
|
||||
// Unpublish translations.
|
||||
foreach ($this->langcodes as $index => $langcode) {
|
||||
if ($index > 0) {
|
||||
$url = $entity->toUrl('edit-form', ['language' => ConfigurableLanguage::load($langcode)]);
|
||||
$edit = ['content_translation[status]' => FALSE];
|
||||
$this->drupalGet($url);
|
||||
$this->submitForm($edit, $this->getFormSubmitAction($entity, $langcode));
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityTypeId);
|
||||
$storage->resetCache([$this->entityId]);
|
||||
$entity = $storage->load($this->entityId);
|
||||
$this->assertFalse($this->manager->getTranslationMetadata($entity->getTranslation($langcode))->isPublished(), 'The translation has been correctly unpublished.');
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the last published translation cannot be unpublished.
|
||||
$this->drupalGet($entity->toUrl('edit-form'));
|
||||
$this->assertSession()->fieldDisabled('content_translation[status]');
|
||||
$this->assertSession()->fieldValueEquals('content_translation[status]', TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the translation authoring information.
|
||||
*/
|
||||
protected function doTestAuthoringInfo() {
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityTypeId);
|
||||
$entity = $storage->load($this->entityId);
|
||||
$values = [];
|
||||
|
||||
// Post different authoring information for each translation.
|
||||
foreach ($this->langcodes as $langcode) {
|
||||
$user = $this->drupalCreateUser();
|
||||
$values[$langcode] = [
|
||||
'uid' => $user->id(),
|
||||
'created' => \Drupal::time()->getRequestTime() - mt_rand(0, 1000),
|
||||
];
|
||||
$edit = [
|
||||
'content_translation[uid]' => $user->getAccountName(),
|
||||
'content_translation[created]' => $this->container->get('date.formatter')->format($values[$langcode]['created'], 'custom', 'Y-m-d H:i:s O'),
|
||||
];
|
||||
$url = $entity->toUrl('edit-form', ['language' => ConfigurableLanguage::load($langcode)]);
|
||||
$this->drupalGet($url);
|
||||
$this->submitForm($edit, $this->getFormSubmitAction($entity, $langcode));
|
||||
}
|
||||
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityTypeId);
|
||||
$entity = $storage->load($this->entityId);
|
||||
foreach ($this->langcodes as $langcode) {
|
||||
$metadata = $this->manager->getTranslationMetadata($entity->getTranslation($langcode));
|
||||
$this->assertEquals($values[$langcode]['uid'], $metadata->getAuthor()->id(), 'Translation author correctly stored.');
|
||||
$this->assertEquals($values[$langcode]['created'], $metadata->getCreatedTime(), 'Translation date correctly stored.');
|
||||
}
|
||||
|
||||
// Try to post non valid values and check that they are rejected.
|
||||
$langcode = 'en';
|
||||
$edit = [
|
||||
// User names have by default length 8.
|
||||
'content_translation[uid]' => $this->randomMachineName(12),
|
||||
'content_translation[created]' => '19/11/1978',
|
||||
];
|
||||
$this->drupalGet($entity->toUrl('edit-form'));
|
||||
$this->submitForm($edit, $this->getFormSubmitAction($entity, $langcode));
|
||||
$this->assertSession()->statusMessageExists('error');
|
||||
$metadata = $this->manager->getTranslationMetadata($entity->getTranslation($langcode));
|
||||
$this->assertEquals($values[$langcode]['uid'], $metadata->getAuthor()->id(), 'Translation author correctly kept.');
|
||||
$this->assertEquals($values[$langcode]['created'], $metadata->getCreatedTime(), 'Translation date correctly kept.');
|
||||
|
||||
// Verify that long usernames can be saved as the translation author.
|
||||
$user = $this->drupalCreateUser([], $this->randomMachineName(UserInterface::USERNAME_MAX_LENGTH));
|
||||
$edit = [
|
||||
// Format the username as it is entered in autocomplete fields.
|
||||
'content_translation[uid]' => $user->getAccountName() . ' (' . $user->id() . ')',
|
||||
'content_translation[created]' => $this->container->get('date.formatter')->format($values[$langcode]['created'], 'custom', 'Y-m-d H:i:s O'),
|
||||
];
|
||||
$this->submitForm($edit, $this->getFormSubmitAction($entity, $langcode));
|
||||
$reloaded_entity = $storage->load($this->entityId);
|
||||
$metadata = $this->manager->getTranslationMetadata($reloaded_entity->getTranslation($langcode));
|
||||
$this->assertEquals($user->id(), $metadata->getAuthor()->id(), 'Translation author correctly set.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests translation deletion.
|
||||
*/
|
||||
protected function doTestTranslationDeletion() {
|
||||
// Confirm and delete a translation.
|
||||
$this->drupalLogin($this->translator);
|
||||
$langcode = 'fr';
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityTypeId);
|
||||
$entity = $storage->load($this->entityId);
|
||||
$language = ConfigurableLanguage::load($langcode);
|
||||
$url = $entity->toUrl('edit-form', ['language' => $language]);
|
||||
$this->drupalGet($url);
|
||||
$this->clickLink('Delete translation');
|
||||
$this->submitForm([], 'Delete ' . $language->getName() . ' translation');
|
||||
$entity = $storage->load($this->entityId, TRUE);
|
||||
$this->assertIsObject($entity);
|
||||
$translations = $entity->getTranslationLanguages();
|
||||
$this->assertCount(2, $translations);
|
||||
$this->assertArrayNotHasKey($langcode, $translations);
|
||||
|
||||
// Check that the translator cannot delete the original translation.
|
||||
$args = [$this->entityTypeId => $entity->id(), 'language' => 'en'];
|
||||
$this->drupalGet(Url::fromRoute("entity.$this->entityTypeId.content_translation_delete", $args));
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of entity field values to be tested.
|
||||
*/
|
||||
protected function getNewEntityValues($langcode) {
|
||||
return [$this->fieldName => [['value' => $this->randomMachineName(16)]]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an edit array containing the values to be posted.
|
||||
*/
|
||||
protected function getEditValues($values, $langcode, $new = FALSE) {
|
||||
$edit = $values[$langcode];
|
||||
$langcode = $new ? LanguageInterface::LANGCODE_NOT_SPECIFIED : $langcode;
|
||||
foreach ($values[$langcode] as $property => $value) {
|
||||
if (is_array($value)) {
|
||||
$edit["{$property}[0][value]"] = $value[0]['value'];
|
||||
unset($edit[$property]);
|
||||
}
|
||||
}
|
||||
return $edit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the form action value when submitting a new translation.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity being tested.
|
||||
* @param string $langcode
|
||||
* Language code for the form.
|
||||
*
|
||||
* @return string
|
||||
* Name of the button to hit.
|
||||
*/
|
||||
protected function getFormSubmitActionForNewTranslation(EntityInterface $entity, $langcode) {
|
||||
$entity->addTranslation($langcode, $entity->toArray());
|
||||
return $this->getFormSubmitAction($entity, $langcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the form action value to be used to submit the entity form.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity being tested.
|
||||
* @param string $langcode
|
||||
* Language code for the form.
|
||||
*
|
||||
* @return string
|
||||
* Name of the button to hit.
|
||||
*/
|
||||
protected function getFormSubmitAction(EntityInterface $entity, $langcode) {
|
||||
return 'Save' . $this->getFormSubmitSuffix($entity, $langcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns appropriate submit button suffix based on translatability.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity being tested.
|
||||
* @param string $langcode
|
||||
* Language code for the form.
|
||||
*
|
||||
* @return string
|
||||
* Submit button suffix based on translatability.
|
||||
*/
|
||||
protected function getFormSubmitSuffix(EntityInterface $entity, $langcode) {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the translation object to use to retrieve the translated values.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity being tested.
|
||||
* @param string $langcode
|
||||
* The language code identifying the translation to be retrieved.
|
||||
*
|
||||
* @return \Drupal\Core\TypedData\TranslatableInterface
|
||||
* The translation object to act on.
|
||||
*/
|
||||
protected function getTranslation(EntityInterface $entity, $langcode) {
|
||||
return $entity->getTranslation($langcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value for the specified property in the given language.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $translation
|
||||
* The translation object the property value should be retrieved from.
|
||||
* @param string $property
|
||||
* The property name.
|
||||
* @param string $langcode
|
||||
* The property value.
|
||||
*
|
||||
* @return mixed
|
||||
* The property value.
|
||||
*/
|
||||
protected function getValue(EntityInterface $translation, $property, $langcode) {
|
||||
$key = $property == 'user_id' ? 'target_id' : 'value';
|
||||
return $translation->get($property)->{$key};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the field that implements the changed timestamp.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity being tested.
|
||||
*
|
||||
* @return string
|
||||
* The field name.
|
||||
*/
|
||||
protected function getChangedFieldName($entity) {
|
||||
return $entity->hasField('content_translation_changed') ? 'content_translation_changed' : 'changed';
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests edit content translation.
|
||||
*/
|
||||
protected function doTestTranslationEdit() {
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityTypeId);
|
||||
$entity = $storage->load($this->entityId);
|
||||
$languages = $this->container->get('language_manager')->getLanguages();
|
||||
|
||||
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->toUrl('edit-form', $options);
|
||||
$this->drupalGet($url);
|
||||
|
||||
$this->assertSession()->responseContains($entity->getTranslation($langcode)->label());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the basic translation workflow.
|
||||
*/
|
||||
protected function doTestTranslationChanged() {
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityTypeId);
|
||||
$entity = $storage->load($this->entityId);
|
||||
$changed_field_name = $this->getChangedFieldName($entity);
|
||||
$definition = $entity->getFieldDefinition($changed_field_name);
|
||||
$config = $definition->getConfig($entity->bundle());
|
||||
|
||||
foreach ([FALSE, TRUE] as $translatable_changed_field) {
|
||||
if ($definition->isTranslatable()) {
|
||||
// For entities defining a translatable changed field we want to test
|
||||
// the correct behavior of that field even if the translatability is
|
||||
// revoked. In that case the changed timestamp should be synchronized
|
||||
// across all translations.
|
||||
$config->setTranslatable($translatable_changed_field);
|
||||
$config->save();
|
||||
}
|
||||
elseif ($translatable_changed_field) {
|
||||
// For entities defining a non-translatable changed field we cannot
|
||||
// declare the field as translatable on the fly by modifying its config
|
||||
// because the schema doesn't support this.
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($entity->getTranslationLanguages() as $language) {
|
||||
// Ensure different timestamps.
|
||||
sleep(1);
|
||||
|
||||
$langcode = $language->getId();
|
||||
|
||||
$edit = [
|
||||
$this->fieldName . '[0][value]' => $this->randomString(),
|
||||
];
|
||||
$edit_path = $entity->toUrl('edit-form', ['language' => $language]);
|
||||
$this->drupalGet($edit_path);
|
||||
$this->submitForm($edit, $this->getFormSubmitAction($entity, $langcode));
|
||||
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityTypeId);
|
||||
$storage->resetCache([$this->entityId]);
|
||||
$entity = $storage->load($this->entityId);
|
||||
$this->assertEquals(
|
||||
$entity->getChangedTimeAcrossTranslations(), $entity->getTranslation($langcode)->getChangedTime(),
|
||||
"Changed time for language {$language->getName()} is the latest change over all languages."
|
||||
);
|
||||
}
|
||||
|
||||
$timestamps = [];
|
||||
foreach ($entity->getTranslationLanguages() as $language) {
|
||||
$next_timestamp = $entity->getTranslation($language->getId())->getChangedTime();
|
||||
if (!in_array($next_timestamp, $timestamps)) {
|
||||
$timestamps[] = $next_timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
if ($translatable_changed_field) {
|
||||
$this->assertSameSize($entity->getTranslationLanguages(), $timestamps, 'All timestamps from all languages are different.');
|
||||
}
|
||||
else {
|
||||
$this->assertCount(1, $timestamps, 'All timestamps from all languages are identical.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the changed time after API and FORM save without changes.
|
||||
*/
|
||||
public function doTestChangedTimeAfterSaveWithoutChanges() {
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityTypeId);
|
||||
$entity = $storage->load($this->entityId);
|
||||
// Test only entities, which implement the EntityChangedInterface.
|
||||
if ($entity instanceof EntityChangedInterface) {
|
||||
$changed_timestamp = $entity->getChangedTime();
|
||||
|
||||
$entity->save();
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityTypeId);
|
||||
$storage->resetCache([$this->entityId]);
|
||||
$entity = $storage->load($this->entityId);
|
||||
$this->assertEquals($changed_timestamp, $entity->getChangedTime(), 'The entity\'s changed time wasn\'t updated after API save without changes.');
|
||||
|
||||
// Ensure different save timestamps.
|
||||
sleep(1);
|
||||
|
||||
// Save the entity on the regular edit form.
|
||||
$language = $entity->language();
|
||||
$edit_path = $entity->toUrl('edit-form', ['language' => $language]);
|
||||
$this->drupalGet($edit_path);
|
||||
$this->submitForm([], $this->getFormSubmitAction($entity, $language->getId()));
|
||||
|
||||
$storage->resetCache([$this->entityId]);
|
||||
$entity = $storage->load($this->entityId);
|
||||
$this->assertNotEquals($changed_timestamp, $entity->getChangedTime(), 'The entity\'s changed time was updated after form save without changes.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Functional;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\Tests\language\Traits\LanguageTestTrait;
|
||||
|
||||
/**
|
||||
* Tests the untranslatable fields behaviors.
|
||||
*
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationUntranslatableFieldsTest extends ContentTranslationPendingRevisionTestBase {
|
||||
|
||||
use LanguageTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['field_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->doSetup();
|
||||
|
||||
// Configure one field as untranslatable.
|
||||
$this->drupalLogin($this->administrator);
|
||||
static::setFieldTranslatable($this->entityTypeId, $this->bundle, $this->fieldName, FALSE);
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager */
|
||||
$entity_field_manager = $this->container->get('entity_field.manager');
|
||||
$entity_field_manager->clearCachedFieldDefinitions();
|
||||
$definitions = $entity_field_manager->getFieldDefinitions($this->entityTypeId, $this->bundle);
|
||||
$this->assertFalse($definitions[$this->fieldName]->isTranslatable());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setupTestFields(): void {
|
||||
parent::setupTestFields();
|
||||
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'field_multilingual',
|
||||
'type' => 'test_field',
|
||||
'entity_type' => $this->entityTypeId,
|
||||
'cardinality' => 1,
|
||||
]);
|
||||
$field_storage->save();
|
||||
FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => $this->bundle,
|
||||
'label' => 'Untranslatable-but-visible test field',
|
||||
'translatable' => FALSE,
|
||||
])->save();
|
||||
\Drupal::service('entity_display.repository')->getFormDisplay($this->entityTypeId, $this->bundle, 'default')
|
||||
->setComponent('field_multilingual', [
|
||||
'type' => 'test_field_widget_multilingual',
|
||||
])
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that hiding untranslatable field widgets works correctly.
|
||||
*/
|
||||
public function testHiddenWidgets(): void {
|
||||
/** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */
|
||||
$entity_type_manager = $this->container->get('entity_type.manager');
|
||||
$id = $this->createEntity(['title' => $this->randomString()], 'en');
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
$entity = $entity_type_manager
|
||||
->getStorage($this->entityTypeId)
|
||||
->load($id);
|
||||
|
||||
// Check that the untranslatable field widget is displayed on the edit form
|
||||
// and no translatability clue is displayed yet.
|
||||
$en_edit_url = $entity->toUrl('edit-form');
|
||||
$this->drupalGet($en_edit_url);
|
||||
$field_xpath = '//input[@name="' . $this->fieldName . '[0][value]"]';
|
||||
$this->assertSession()->elementExists('xpath', $field_xpath);
|
||||
$clue_xpath = '//label[@for="edit-' . strtr($this->fieldName, '_', '-') . '-0-value"]/span[text()="(all languages)"]';
|
||||
$this->assertSession()->elementNotExists('xpath', $clue_xpath);
|
||||
$this->assertSession()->pageTextContains('Untranslatable-but-visible test field');
|
||||
|
||||
// Add a translation and check that the untranslatable field widget is
|
||||
// displayed on the translation and edit forms along with translatability
|
||||
// clues.
|
||||
$add_url = Url::fromRoute("entity.{$this->entityTypeId}.content_translation_add", [
|
||||
$entity->getEntityTypeId() => $entity->id(),
|
||||
'source' => 'en',
|
||||
'target' => 'it',
|
||||
]);
|
||||
$this->drupalGet($add_url);
|
||||
$this->assertSession()->elementExists('xpath', $field_xpath);
|
||||
$this->assertSession()->elementExists('xpath', $clue_xpath);
|
||||
$this->assertSession()->pageTextContains('Untranslatable-but-visible test field');
|
||||
$this->submitForm([], 'Save');
|
||||
|
||||
// Check that the widget is displayed along with its clue in the edit form
|
||||
// for both languages.
|
||||
$this->drupalGet($en_edit_url);
|
||||
$this->assertSession()->elementExists('xpath', $field_xpath);
|
||||
$this->assertSession()->elementExists('xpath', $clue_xpath);
|
||||
$it_edit_url = $entity->toUrl('edit-form', ['language' => ConfigurableLanguage::load('it')]);
|
||||
$this->drupalGet($it_edit_url);
|
||||
$this->assertSession()->elementExists('xpath', $field_xpath);
|
||||
$this->assertSession()->elementExists('xpath', $clue_xpath);
|
||||
|
||||
// Configure untranslatable field widgets to be hidden on non-default
|
||||
// language edit forms.
|
||||
$settings_key = 'settings[' . $this->entityTypeId . '][' . $this->bundle . '][settings][content_translation][untranslatable_fields_hide]';
|
||||
$settings_url = 'admin/config/regional/content-language';
|
||||
$this->drupalGet($settings_url);
|
||||
$this->submitForm([$settings_key => 1], 'Save configuration');
|
||||
|
||||
// Verify that the widget is displayed in the default language edit form,
|
||||
// but no clue is displayed.
|
||||
$this->drupalGet($en_edit_url);
|
||||
$field_xpath = '//input[@name="' . $this->fieldName . '[0][value]"]';
|
||||
$this->assertSession()->elementExists('xpath', $field_xpath);
|
||||
$this->assertSession()->elementNotExists('xpath', $clue_xpath);
|
||||
$this->assertSession()->pageTextContains('Untranslatable-but-visible test field');
|
||||
|
||||
// Verify no widget is displayed on the non-default language edit form.
|
||||
$this->drupalGet($it_edit_url);
|
||||
$this->assertSession()->elementNotExists('xpath', $field_xpath);
|
||||
$this->assertSession()->elementNotExists('xpath', $clue_xpath);
|
||||
$this->assertSession()->pageTextContains('Untranslatable-but-visible test field');
|
||||
|
||||
// Verify a warning is displayed.
|
||||
$this->assertSession()->statusMessageContains('Fields that apply to all languages are hidden to avoid conflicting changes.', 'warning');
|
||||
$this->assertSession()->elementExists('xpath', '//a[@href="' . $entity->toUrl('edit-form')->toString() . '" and text()="Edit them on the original language form"]');
|
||||
|
||||
// Configure untranslatable field widgets to be displayed on non-default
|
||||
// language edit forms.
|
||||
$this->drupalGet($settings_url);
|
||||
$this->submitForm([$settings_key => 0], 'Save configuration');
|
||||
|
||||
// Check that the widget is displayed along with its clue in the edit form
|
||||
// for both languages.
|
||||
$this->drupalGet($en_edit_url);
|
||||
$this->assertSession()->elementExists('xpath', $field_xpath);
|
||||
$this->assertSession()->elementExists('xpath', $clue_xpath);
|
||||
$this->drupalGet($it_edit_url);
|
||||
$this->assertSession()->elementExists('xpath', $field_xpath);
|
||||
$this->assertSession()->elementExists('xpath', $clue_xpath);
|
||||
|
||||
// Enable content moderation and verify that widgets are hidden despite them
|
||||
// being configured to be displayed.
|
||||
$this->enableContentModeration();
|
||||
$this->drupalGet($it_edit_url);
|
||||
$this->assertSession()->elementNotExists('xpath', $field_xpath);
|
||||
$this->assertSession()->elementNotExists('xpath', $clue_xpath);
|
||||
|
||||
// Verify a warning is displayed.
|
||||
$this->assertSession()->statusMessageContains('Fields that apply to all languages are hidden to avoid conflicting changes.', 'warning');
|
||||
$this->assertSession()->elementExists('xpath', '//a[@href="' . $entity->toUrl('edit-form')->toString() . '" and text()="Edit them on the original language form"]');
|
||||
|
||||
// Verify that checkboxes on the language content settings page are checked
|
||||
// and disabled for moderated bundles.
|
||||
$this->drupalGet($settings_url);
|
||||
$field_name = "settings[{$this->entityTypeId}][{$this->bundle}][settings][content_translation][untranslatable_fields_hide]";
|
||||
$this->assertSession()->fieldValueEquals($field_name, 1);
|
||||
$this->assertSession()->fieldDisabled($field_name);
|
||||
$this->submitForm([$settings_key => 0], 'Save configuration');
|
||||
$this->assertSession()->fieldValueEquals($field_name, 1);
|
||||
$this->assertSession()->fieldDisabled($field_name);
|
||||
|
||||
// Verify that the untranslatable fields warning message is not displayed
|
||||
// when submitting.
|
||||
$this->drupalGet($it_edit_url);
|
||||
$this->assertSession()->pageTextContains('Fields that apply to all languages are hidden to avoid conflicting changes.');
|
||||
$this->submitForm([], 'Save (this translation)');
|
||||
$this->assertSession()->pageTextNotContains('Fields that apply to all languages are hidden to avoid conflicting changes.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,428 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Functional;
|
||||
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\entity_test\Entity\EntityTestMulRevPub;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait;
|
||||
use Drupal\user\UserInterface;
|
||||
|
||||
/**
|
||||
* Tests the content translation workflows for the test entity.
|
||||
*
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationWorkflowsTest extends ContentTranslationTestBase {
|
||||
|
||||
use AssertPageCacheContextsAndTagsTrait;
|
||||
|
||||
/**
|
||||
* The entity used for testing.
|
||||
*
|
||||
* @var \Drupal\entity_test\Entity\EntityTestMul
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $entityTypeId = 'entity_test_mulrevpub';
|
||||
|
||||
/**
|
||||
* The referencing entity.
|
||||
*
|
||||
* @var \Drupal\entity_test\Entity\EntityTestMul
|
||||
*/
|
||||
protected $referencingEntity;
|
||||
|
||||
/**
|
||||
* The entity owner account to be used to test multilingual entity editing.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $entityOwner;
|
||||
|
||||
/**
|
||||
* The user that has entity owner permission but is not the owner.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $notEntityOwner;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'language',
|
||||
'content_translation',
|
||||
'entity_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->doSetup();
|
||||
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'field_reference',
|
||||
'type' => 'entity_reference',
|
||||
'entity_type' => $this->entityTypeId,
|
||||
'cardinality' => 1,
|
||||
'settings' => [
|
||||
'target_type' => $this->entityTypeId,
|
||||
],
|
||||
]);
|
||||
$field_storage->save();
|
||||
FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => $this->entityTypeId,
|
||||
'label' => 'Reference',
|
||||
'translatable' => FALSE,
|
||||
])->save();
|
||||
|
||||
$this->container->get('entity_display.repository')
|
||||
->getViewDisplay($this->entityTypeId, $this->entityTypeId, 'default')
|
||||
->setComponent('field_reference', [
|
||||
'type' => 'entity_reference_entity_view',
|
||||
])
|
||||
->save();
|
||||
|
||||
$this->setupEntity();
|
||||
|
||||
// Create a second entity that references the first to test how the
|
||||
// translation can be viewed through an entity reference field.
|
||||
$this->referencingEntity = EntityTestMulRevPub::create([
|
||||
'name' => 'referencing',
|
||||
'field_reference' => $this->entity->id(),
|
||||
]);
|
||||
$this->referencingEntity->addTranslation($this->langcodes[2], $this->referencingEntity->toArray());
|
||||
$this->referencingEntity->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setupUsers(): void {
|
||||
$this->entityOwner = $this->drupalCreateUser($this->getEntityOwnerPermissions(), 'entity_owner');
|
||||
$this->notEntityOwner = $this->drupalCreateUser();
|
||||
$this->notEntityOwner->set('roles', $this->entityOwner->getRoles(TRUE));
|
||||
$this->notEntityOwner->save();
|
||||
parent::setupUsers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of permissions needed for the entity owner.
|
||||
*/
|
||||
protected function getEntityOwnerPermissions(): array {
|
||||
return ['edit own entity_test content', 'translate editable entities', 'view test entity', 'view test entity translations', 'view unpublished test entity translations'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getTranslatorPermissions() {
|
||||
$permissions = parent::getTranslatorPermissions();
|
||||
$permissions[] = 'view test entity';
|
||||
$permissions[] = 'view test entity translations';
|
||||
$permissions[] = 'view unpublished test entity translations';
|
||||
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEditorPermissions(): array {
|
||||
return ['administer entity_test content', 'view test entity', 'view test entity translations'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a test entity and translate it.
|
||||
*
|
||||
* @param \Drupal\User\UserInterface|null $user
|
||||
* (optional) The entity owner.
|
||||
*/
|
||||
protected function setupEntity(?UserInterface $user = NULL): void {
|
||||
$default_langcode = $this->langcodes[0];
|
||||
|
||||
// Create a test entity.
|
||||
$user = $user ?: $this->drupalCreateUser();
|
||||
$values = [
|
||||
'name' => $this->randomMachineName(),
|
||||
'user_id' => $user->id(),
|
||||
$this->fieldName => [['value' => $this->randomMachineName(16)]],
|
||||
];
|
||||
$id = $this->createEntity($values, $default_langcode);
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityTypeId);
|
||||
|
||||
// Create a translation that is not published to test view access.
|
||||
$this->drupalLogin($this->translator);
|
||||
$add_translation_url = Url::fromRoute("entity.$this->entityTypeId.content_translation_add", [$this->entityTypeId => $id, 'source' => $default_langcode, 'target' => $this->langcodes[2]]);
|
||||
$edit = [
|
||||
'name[0][value]' => 'translation name',
|
||||
'content_translation[status]' => FALSE,
|
||||
];
|
||||
$this->drupalGet($add_translation_url);
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
$storage->resetCache([$id]);
|
||||
$this->entity = $storage->load($id);
|
||||
|
||||
$this->rebuildContainer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests simple and editorial translation workflows.
|
||||
*/
|
||||
public function testWorkflows(): void {
|
||||
// Test workflows for the editor.
|
||||
$expected_status = [
|
||||
'edit' => 200,
|
||||
'delete' => 200,
|
||||
'overview' => 403,
|
||||
'add_translation' => 403,
|
||||
'edit_translation' => 403,
|
||||
'delete_translation' => 403,
|
||||
'view_unpublished_translation' => 403,
|
||||
'view_unpublished_translation_reference' => FALSE,
|
||||
];
|
||||
$this->doTestWorkflows($this->editor, $expected_status);
|
||||
|
||||
// Test workflows for the translator.
|
||||
$expected_status = [
|
||||
'edit' => 403,
|
||||
'delete' => 403,
|
||||
'overview' => 200,
|
||||
'add_translation' => 200,
|
||||
'edit_translation' => 200,
|
||||
'delete_translation' => 200,
|
||||
'view_unpublished_translation' => 200,
|
||||
'view_unpublished_translation_reference' => TRUE,
|
||||
];
|
||||
$this->doTestWorkflows($this->translator, $expected_status);
|
||||
|
||||
// Test workflows for the admin.
|
||||
$expected_status = [
|
||||
'edit' => 200,
|
||||
'delete' => 200,
|
||||
'overview' => 200,
|
||||
'add_translation' => 200,
|
||||
'edit_translation' => 403,
|
||||
'delete_translation' => 403,
|
||||
'view_unpublished_translation' => 200,
|
||||
'view_unpublished_translation_reference' => TRUE,
|
||||
];
|
||||
$this->doTestWorkflows($this->administrator, $expected_status);
|
||||
|
||||
// Check that translation permissions allow the associated operations.
|
||||
$ops = ['create' => 'Add', 'update' => 'Edit', 'delete' => 'Delete'];
|
||||
$translations_url = $this->entity->toUrl('drupal:content-translation-overview');
|
||||
foreach ($ops as $current_op => $item) {
|
||||
$user = $this->drupalCreateUser([
|
||||
$this->getTranslatePermission(),
|
||||
"$current_op content translations",
|
||||
'view test entity',
|
||||
]);
|
||||
$this->drupalLogin($user);
|
||||
$this->drupalGet($translations_url);
|
||||
|
||||
// Make sure that the user.permissions cache context and the cache tags
|
||||
// for the entity are present.
|
||||
$this->assertCacheContext('user.permissions');
|
||||
foreach ($this->entity->getCacheTags() as $cache_tag) {
|
||||
$this->assertSession()->responseHeaderContains('X-Drupal-Cache-Tags', $cache_tag);
|
||||
}
|
||||
|
||||
foreach ($ops as $op => $label) {
|
||||
if ($op != $current_op) {
|
||||
$this->assertSession()->linkNotExists($label);
|
||||
}
|
||||
else {
|
||||
$this->assertSession()->linkExists($label, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test workflows for the entity owner with non-editable content.
|
||||
$expected_status = [
|
||||
'edit' => 403,
|
||||
'delete' => 403,
|
||||
'overview' => 403,
|
||||
'add_translation' => 403,
|
||||
'edit_translation' => 403,
|
||||
'delete_translation' => 403,
|
||||
'view_unpublished_translation' => 200,
|
||||
'view_unpublished_translation_reference' => TRUE,
|
||||
];
|
||||
$this->doTestWorkflows($this->entityOwner, $expected_status);
|
||||
|
||||
// Test workflows for the entity owner with editable content.
|
||||
$this->setupEntity($this->entityOwner);
|
||||
$this->referencingEntity->set('field_reference', $this->entity->id());
|
||||
$this->referencingEntity->save();
|
||||
$expected_status = [
|
||||
'edit' => 200,
|
||||
'delete' => 403,
|
||||
'overview' => 200,
|
||||
'add_translation' => 200,
|
||||
'edit_translation' => 200,
|
||||
'delete_translation' => 200,
|
||||
'view_unpublished_translation' => 200,
|
||||
'view_unpublished_translation_reference' => TRUE,
|
||||
];
|
||||
$this->doTestWorkflows($this->entityOwner, $expected_status);
|
||||
$expected_status = [
|
||||
'edit' => 403,
|
||||
'delete' => 403,
|
||||
'overview' => 403,
|
||||
'add_translation' => 403,
|
||||
'edit_translation' => 403,
|
||||
'delete_translation' => 403,
|
||||
'view_unpublished_translation' => 200,
|
||||
'view_unpublished_translation_reference' => TRUE,
|
||||
];
|
||||
$this->doTestWorkflows($this->notEntityOwner, $expected_status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that workflows have the expected behaviors for the given user.
|
||||
*
|
||||
* @param \Drupal\user\UserInterface $user
|
||||
* The user to test the workflow behavior against.
|
||||
* @param array $expected_status
|
||||
* The an associative array with the operation name as key and the expected
|
||||
* status as value.
|
||||
*/
|
||||
protected function doTestWorkflows(UserInterface $user, $expected_status): void {
|
||||
$default_langcode = $this->langcodes[0];
|
||||
$languages = $this->container->get('language_manager')->getLanguages();
|
||||
$options = ['language' => $languages[$default_langcode], 'absolute' => TRUE];
|
||||
$this->drupalLogin($user);
|
||||
|
||||
// Check whether the user is allowed to access the entity form in edit mode.
|
||||
$edit_url = $this->entity->toUrl('edit-form', $options);
|
||||
$this->drupalGet($edit_url, $options);
|
||||
$this->assertSession()->statusCodeEquals($expected_status['edit']);
|
||||
|
||||
// Check whether the user is allowed to access the entity delete form.
|
||||
$delete_url = $this->entity->toUrl('delete-form', $options);
|
||||
$this->drupalGet($delete_url, $options);
|
||||
$this->assertSession()->statusCodeEquals($expected_status['delete']);
|
||||
|
||||
// Check whether the user is allowed to access the translation overview.
|
||||
$langcode = $this->langcodes[1];
|
||||
$options['language'] = $languages[$langcode];
|
||||
$translations_url = $this->entity->toUrl('drupal:content-translation-overview', $options)->toString();
|
||||
$this->drupalGet($translations_url);
|
||||
$this->assertSession()->statusCodeEquals($expected_status['overview']);
|
||||
|
||||
// Check whether the user is allowed to create a translation.
|
||||
$add_translation_url = Url::fromRoute("entity.$this->entityTypeId.content_translation_add", [$this->entityTypeId => $this->entity->id(), 'source' => $default_langcode, 'target' => $langcode], $options);
|
||||
if ($expected_status['add_translation'] == 200) {
|
||||
$this->clickLink('Add');
|
||||
$this->assertSession()->addressEquals($add_translation_url);
|
||||
// Check that the translation form does not contain shared elements for
|
||||
// translators.
|
||||
if ($expected_status['edit'] == 403) {
|
||||
$this->assertNoSharedElements();
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->drupalGet($add_translation_url);
|
||||
}
|
||||
$this->assertSession()->statusCodeEquals($expected_status['add_translation']);
|
||||
|
||||
// Check whether the user is allowed to edit a translation.
|
||||
$langcode = $this->langcodes[2];
|
||||
$options['language'] = $languages[$langcode];
|
||||
$edit_translation_url = Url::fromRoute("entity.$this->entityTypeId.content_translation_edit", [$this->entityTypeId => $this->entity->id(), 'language' => $langcode], $options);
|
||||
if ($expected_status['edit_translation'] == 200) {
|
||||
$this->drupalGet($translations_url);
|
||||
$editor = $expected_status['edit'] == 200;
|
||||
|
||||
if ($editor) {
|
||||
$this->clickLink('Edit', 1);
|
||||
// An editor should be pointed to the entity form in multilingual mode.
|
||||
// We need a new expected edit path with a new language.
|
||||
$expected_edit_path = $this->entity->toUrl('edit-form', $options)->toString();
|
||||
$this->assertSession()->addressEquals($expected_edit_path);
|
||||
}
|
||||
else {
|
||||
$this->clickLink('Edit');
|
||||
// While a translator should be pointed to the translation form.
|
||||
$this->assertSession()->addressEquals($edit_translation_url);
|
||||
// Check that the translation form does not contain shared elements.
|
||||
$this->assertNoSharedElements();
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->drupalGet($edit_translation_url);
|
||||
}
|
||||
$this->assertSession()->statusCodeEquals($expected_status['edit_translation']);
|
||||
|
||||
// When viewing an unpublished entity directly, access is currently denied
|
||||
// completely. See https://www.drupal.org/node/2978048.
|
||||
$this->drupalGet($this->entity->getTranslation($langcode)->toUrl());
|
||||
$this->assertSession()->statusCodeEquals($expected_status['view_unpublished_translation']);
|
||||
|
||||
// On a reference field, the translation falls back to the default language.
|
||||
$this->drupalGet($this->referencingEntity->getTranslation($langcode)->toUrl());
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
if ($expected_status['view_unpublished_translation_reference']) {
|
||||
$this->assertSession()->pageTextContains('translation name');
|
||||
}
|
||||
else {
|
||||
$this->assertSession()->pageTextContains($this->entity->label());
|
||||
}
|
||||
|
||||
// Check whether the user is allowed to delete a translation.
|
||||
$delete_translation_url = Url::fromRoute("entity.$this->entityTypeId.content_translation_delete", [$this->entityTypeId => $this->entity->id(), 'language' => $langcode], $options);
|
||||
if ($expected_status['delete_translation'] == 200) {
|
||||
$this->drupalGet($translations_url);
|
||||
$editor = $expected_status['delete'] == 200;
|
||||
|
||||
if ($editor) {
|
||||
$this->clickLink('Delete', 1);
|
||||
// An editor should be pointed to the entity deletion form in
|
||||
// multilingual mode. We need a new expected delete path with a new
|
||||
// language.
|
||||
$expected_delete_path = $this->entity->toUrl('delete-form', $options)->toString();
|
||||
$this->assertSession()->addressEquals($expected_delete_path);
|
||||
}
|
||||
else {
|
||||
$this->clickLink('Delete');
|
||||
// While a translator should be pointed to the translation deletion
|
||||
// form.
|
||||
$this->assertSession()->addressEquals($delete_translation_url);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->drupalGet($delete_translation_url);
|
||||
}
|
||||
$this->assertSession()->statusCodeEquals($expected_status['delete_translation']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the current page does not contain shared form elements.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertNoSharedElements(): void {
|
||||
$language_none = LanguageInterface::LANGCODE_NOT_SPECIFIED;
|
||||
$this->assertSession()->fieldNotExists("field_test_text[$language_none][0][value]");
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Functional;
|
||||
|
||||
use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
|
||||
|
||||
/**
|
||||
* Generic module test for content_translation.
|
||||
*
|
||||
* @group content_translation
|
||||
*/
|
||||
class GenericTest extends GenericModuleTestBase {}
|
||||
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Functional\Views;
|
||||
|
||||
use Drupal\Tests\views_ui\Functional\UITestBase;
|
||||
|
||||
/**
|
||||
* Tests the views UI when content_translation is enabled.
|
||||
*
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationViewsUITest extends UITestBase {
|
||||
|
||||
/**
|
||||
* Views used by this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $testViews = ['test_view'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['content_translation'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests the views UI.
|
||||
*/
|
||||
public function testViewsUI(): void {
|
||||
$this->drupalGet('admin/structure/views/view/test_view/edit');
|
||||
$this->assertSession()->titleEquals('Test view (Views test data) | Drupal');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Functional\Views;
|
||||
|
||||
use Drupal\Tests\content_translation\Functional\ContentTranslationTestBase;
|
||||
use Drupal\views\Tests\ViewTestData;
|
||||
use Drupal\Core\Language\Language;
|
||||
use Drupal\user\Entity\User;
|
||||
|
||||
/**
|
||||
* Tests the content translation overview link field handler.
|
||||
*
|
||||
* @group content_translation
|
||||
* @see \Drupal\content_translation\Plugin\views\field\TranslationLink
|
||||
*/
|
||||
class TranslationLinkTest extends ContentTranslationTestBase {
|
||||
|
||||
/**
|
||||
* Views used by this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $testViews = ['test_entity_translations_link'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['content_translation_test_views'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
// @todo Use entity_type once it has multilingual Views integration.
|
||||
$this->entityTypeId = 'user';
|
||||
|
||||
parent::setUp();
|
||||
$this->doSetup();
|
||||
|
||||
// Assign user 1 a language code so that the entity can be translated.
|
||||
$user = User::load(1);
|
||||
$user->langcode = 'en';
|
||||
$user->save();
|
||||
|
||||
// Assign user 2 LANGCODE_NOT_SPECIFIED code so entity can't be translated.
|
||||
$user = User::load(2);
|
||||
$user->langcode = Language::LANGCODE_NOT_SPECIFIED;
|
||||
$user->save();
|
||||
|
||||
ViewTestData::createTestViews(static::class, ['content_translation_test_views']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getTranslatorPermissions() {
|
||||
$permissions = parent::getTranslatorPermissions();
|
||||
$permissions[] = 'access user profiles';
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the content translation overview link field handler.
|
||||
*/
|
||||
public function testTranslationLink(): void {
|
||||
$this->drupalGet('test-entity-translations-link');
|
||||
$this->assertSession()->linkByHrefExists('user/1/translations');
|
||||
$this->assertSession()->linkByHrefNotExists('user/2/translations', 'The translations link is not present when content_translation_translate_access() is FALSE.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\FunctionalJavascript;
|
||||
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
|
||||
/**
|
||||
* Tests that the content translation configuration javascript does't fail.
|
||||
*
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationConfigUITest extends WebDriverTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['content_translation', 'node'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Use the minimal profile.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $profile = 'standard';
|
||||
|
||||
/**
|
||||
* Tests that the content translation configuration javascript does't fail.
|
||||
*/
|
||||
public function testContentTranslationConfigUI(): void {
|
||||
$content_translation_manager = $this->container->get('content_translation.manager');
|
||||
$content_translation_manager->setEnabled('node', 'article', TRUE);
|
||||
$this->rebuildContainer();
|
||||
|
||||
$admin = $this->drupalCreateUser([], NULL, TRUE);
|
||||
$this->drupalLogin($admin);
|
||||
$this->drupalGet('/admin/config/regional/content-language');
|
||||
$this->failOnJavaScriptErrors();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\FunctionalJavascript;
|
||||
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
|
||||
/**
|
||||
* Tests that contextual links are available for content translation.
|
||||
*
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationContextualLinksTest extends WebDriverTestBase {
|
||||
|
||||
/**
|
||||
* The 'translator' user to use during testing.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $translator;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['content_translation', 'contextual', 'node'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Set up an additional language.
|
||||
ConfigurableLanguage::createFromLangcode('es')->save();
|
||||
|
||||
// Create a content type.
|
||||
$this->drupalCreateContentType(['type' => 'page']);
|
||||
|
||||
// Enable content translation.
|
||||
$content_translation_manager = $this->container->get('content_translation.manager');
|
||||
$content_translation_manager->setEnabled('node', 'page', TRUE);
|
||||
$this->rebuildContainer();
|
||||
|
||||
// Create a translator user.
|
||||
$permissions = [
|
||||
'access contextual links',
|
||||
'administer nodes',
|
||||
'edit any page content',
|
||||
'translate any entity',
|
||||
];
|
||||
$this->translator = $this->drupalCreateUser($permissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a contextual link is available for translating a node.
|
||||
*/
|
||||
public function testContentTranslationContextualLinks(): void {
|
||||
$node = $this->drupalCreateNode(['type' => 'page', 'title' => 'Test']);
|
||||
|
||||
// Check that the translate link appears on the node page.
|
||||
$this->drupalLogin($this->translator);
|
||||
$this->drupalGet('node/' . $node->id());
|
||||
$link = $this->assertSession()->waitForElement('css', '[data-contextual-id^="node:node=1"] .contextual-links a:contains("Translate")');
|
||||
$this->assertStringContainsString('node/1/translations', $link->getAttribute('href'));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Kernel;
|
||||
|
||||
use Drupal\Core\Config\ConfigImporter;
|
||||
use Drupal\Core\Config\StorageComparer;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests content translation updates performed during config import.
|
||||
*
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationConfigImportTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* Config Importer object used for testing.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigImporter
|
||||
*/
|
||||
protected $configImporter;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'system',
|
||||
'user',
|
||||
'entity_test',
|
||||
'language',
|
||||
'content_translation',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->installConfig(['system']);
|
||||
$this->installEntitySchema('entity_test_mul');
|
||||
$this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.sync'));
|
||||
|
||||
// Set up the ConfigImporter object for testing.
|
||||
$storage_comparer = new StorageComparer(
|
||||
$this->container->get('config.storage.sync'),
|
||||
$this->container->get('config.storage')
|
||||
);
|
||||
$this->configImporter = new ConfigImporter(
|
||||
$storage_comparer->createChangelist(),
|
||||
$this->container->get('event_dispatcher'),
|
||||
$this->container->get('config.manager'),
|
||||
$this->container->get('lock'),
|
||||
$this->container->get('config.typed'),
|
||||
$this->container->get('module_handler'),
|
||||
$this->container->get('module_installer'),
|
||||
$this->container->get('theme_handler'),
|
||||
$this->container->get('string_translation'),
|
||||
$this->container->get('extension.list.module'),
|
||||
$this->container->get('extension.list.theme')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests config import updates.
|
||||
*/
|
||||
public function testConfigImportUpdates(): void {
|
||||
$entity_type_id = 'entity_test_mul';
|
||||
$config_id = $entity_type_id . '.' . $entity_type_id;
|
||||
$config_name = 'language.content_settings.' . $config_id;
|
||||
$storage = $this->container->get('config.storage');
|
||||
$sync = $this->container->get('config.storage.sync');
|
||||
|
||||
// Verify the configuration to create does not exist yet.
|
||||
$this->assertFalse($storage->exists($config_name), $config_name . ' not found.');
|
||||
|
||||
// Create new config entity.
|
||||
$data = [
|
||||
'uuid' => 'a019d89b-c4d9-4ed4-b859-894e4e2e93cf',
|
||||
'langcode' => 'en',
|
||||
'status' => TRUE,
|
||||
'dependencies' => [
|
||||
'module' => ['content_translation'],
|
||||
],
|
||||
'id' => $config_id,
|
||||
'target_entity_type_id' => 'entity_test_mul',
|
||||
'target_bundle' => 'entity_test_mul',
|
||||
'default_langcode' => 'site_default',
|
||||
'language_alterable' => FALSE,
|
||||
'third_party_settings' => [
|
||||
'content_translation' => ['enabled' => TRUE],
|
||||
],
|
||||
];
|
||||
$sync->write($config_name, $data);
|
||||
$this->assertTrue($sync->exists($config_name), $config_name . ' found.');
|
||||
|
||||
// Import.
|
||||
$this->configImporter->reset()->import();
|
||||
|
||||
// Verify the values appeared.
|
||||
$config = $this->config($config_name);
|
||||
$this->assertSame($config_id, $config->get('id'));
|
||||
|
||||
// Verify that updates were performed.
|
||||
$entity_type = $this->container->get('entity_type.manager')->getDefinition($entity_type_id);
|
||||
$table = $entity_type->getDataTable();
|
||||
$db_schema = $this->container->get('database')->schema();
|
||||
$result = $db_schema->fieldExists($table, 'content_translation_source') && $db_schema->fieldExists($table, 'content_translation_outdated');
|
||||
$this->assertTrue($result, 'Content translation updates were successfully performed during config import.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Kernel;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\entity_test\Entity\EntityTestMul;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
|
||||
/**
|
||||
* Tests the Content Translation bundle info logic.
|
||||
*
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationEntityBundleInfoTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'system',
|
||||
'node',
|
||||
'user',
|
||||
'language',
|
||||
'content_translation_test',
|
||||
'content_translation',
|
||||
'entity_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* The content translation manager.
|
||||
*
|
||||
* @var \Drupal\content_translation\ContentTranslationManagerInterface
|
||||
*/
|
||||
protected $contentTranslationManager;
|
||||
|
||||
/**
|
||||
* The bundle info service.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeBundleInfo
|
||||
*/
|
||||
protected $bundleInfo;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->contentTranslationManager = $this->container->get('content_translation.manager');
|
||||
$this->bundleInfo = $this->container->get('entity_type.bundle.info');
|
||||
|
||||
$this->installEntitySchema('entity_test_mul');
|
||||
|
||||
ConfigurableLanguage::createFromLangcode('it')->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that modules can know whether bundles are translatable.
|
||||
*/
|
||||
public function testHookInvocationOrder(): void {
|
||||
$this->contentTranslationManager->setEnabled('entity_test_mul', 'entity_test_mul', TRUE);
|
||||
$this->bundleInfo->clearCachedBundles();
|
||||
$this->bundleInfo->getAllBundleInfo();
|
||||
|
||||
// Verify that the test module comes first in the module list, which would
|
||||
// normally make its hook implementation to be invoked first.
|
||||
/** @var \Drupal\Core\Extension\ModuleHandlerInterface $module_handler */
|
||||
$module_handler = $this->container->get('module_handler');
|
||||
$module_list = $module_handler->getModuleList();
|
||||
$expected_modules = [
|
||||
'content_translation_test',
|
||||
'content_translation',
|
||||
];
|
||||
$actual_modules = array_keys(array_intersect_key($module_list, array_flip($expected_modules)));
|
||||
$this->assertEquals($expected_modules, $actual_modules);
|
||||
|
||||
// Check that the "content_translation_test" hook implementation has access
|
||||
// to the "translatable" bundle info property.
|
||||
/** @var \Drupal\Core\State\StateInterface $state */
|
||||
$state = $this->container->get('state');
|
||||
$this->assertTrue($state->get('content_translation_test.translatable'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that field synchronization is skipped for disabled bundles.
|
||||
*/
|
||||
public function testFieldSynchronizationWithDisabledBundle(): void {
|
||||
$entity = EntityTestMul::create();
|
||||
$entity->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $translation */
|
||||
$translation = $entity->addTranslation('it');
|
||||
$translation->save();
|
||||
|
||||
$this->assertTrue($entity->isTranslatable());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that bundle translation settings are propagated on creation.
|
||||
*
|
||||
* @throws \Drupal\Core\Entity\EntityStorageException
|
||||
*/
|
||||
public function testBundleClearOnLanguageContentSettingInsert(): void {
|
||||
$node = $this->getBundledNode();
|
||||
$this->assertFalse($node->isTranslatable());
|
||||
$this->contentTranslationManager->setEnabled('node', 'bundle_test', TRUE);
|
||||
$this->assertTrue($node->isTranslatable(), "Bundle info was not cleared on language_content_settings creation.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that bundle translation setting changes are propagated.
|
||||
*
|
||||
* @throws \Drupal\Core\Entity\EntityStorageException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testBundleClearOnLanguageContentSettingUpdate(): void {
|
||||
$node = $this->getBundledNode();
|
||||
$this->assertFalse($node->isTranslatable());
|
||||
$this->container->get('entity_type.manager')->getStorage('language_content_settings')->create([
|
||||
'target_entity_type_id' => 'node',
|
||||
'target_bundle' => 'bundle_test',
|
||||
])->save();
|
||||
$this->assertFalse($node->isTranslatable());
|
||||
$this->contentTranslationManager->setEnabled('node', 'bundle_test', TRUE);
|
||||
$this->assertTrue($node->isTranslatable(), "Bundle info was not cleared on language_content_settings update.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a new bundled node for testing.
|
||||
*
|
||||
* @return \Drupal\node\Entity\Node
|
||||
* The new node.
|
||||
*
|
||||
* @throws \Drupal\Core\Entity\EntityStorageException
|
||||
*/
|
||||
protected function getBundledNode() {
|
||||
$this->installEntitySchema('node');
|
||||
$bundle = NodeType::create([
|
||||
'type' => 'bundle_test',
|
||||
'name' => 'Bundle Test',
|
||||
]);
|
||||
$bundle->save();
|
||||
$node = Node::create([
|
||||
'type' => 'bundle_test',
|
||||
]);
|
||||
return $node;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,581 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Kernel;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityConstraintViolationListInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\entity_test\Entity\EntityTestMulRev;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\Tests\TestFileCreationTrait;
|
||||
use Drupal\user\Entity\User;
|
||||
|
||||
/**
|
||||
* Tests the field synchronization logic when revisions are involved.
|
||||
*
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationFieldSyncRevisionTest extends EntityKernelTestBase {
|
||||
|
||||
use TestFileCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'file',
|
||||
'image',
|
||||
'language',
|
||||
'content_translation',
|
||||
'content_translation_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* The synchronized field name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName = 'sync_field';
|
||||
|
||||
/**
|
||||
* The content translation manager.
|
||||
*
|
||||
* @var \Drupal\content_translation\ContentTranslationManagerInterface|\Drupal\content_translation\BundleTranslationSettingsInterface
|
||||
*/
|
||||
protected $contentTranslationManager;
|
||||
|
||||
/**
|
||||
* The test entity storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\ContentEntityStorageInterface
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$entity_type_id = 'entity_test_mulrev';
|
||||
$this->installEntitySchema($entity_type_id);
|
||||
$this->installEntitySchema('file');
|
||||
$this->installSchema('file', ['file_usage']);
|
||||
|
||||
ConfigurableLanguage::createFromLangcode('it')->save();
|
||||
ConfigurableLanguage::createFromLangcode('fr')->save();
|
||||
|
||||
/** @var \Drupal\field\Entity\FieldStorageConfig $field_storage */
|
||||
$field_storage_config = FieldStorageConfig::create([
|
||||
'field_name' => $this->fieldName,
|
||||
'type' => 'image',
|
||||
'entity_type' => $entity_type_id,
|
||||
'cardinality' => 1,
|
||||
'translatable' => 1,
|
||||
]);
|
||||
$field_storage_config->save();
|
||||
|
||||
$field_config = FieldConfig::create([
|
||||
'entity_type' => $entity_type_id,
|
||||
'field_name' => $this->fieldName,
|
||||
'bundle' => $entity_type_id,
|
||||
'label' => 'Synchronized field',
|
||||
'translatable' => 1,
|
||||
]);
|
||||
$field_config->save();
|
||||
|
||||
$property_settings = [
|
||||
'alt' => 'alt',
|
||||
'title' => 'title',
|
||||
'file' => 0,
|
||||
];
|
||||
$field_config->setThirdPartySetting('content_translation', 'translation_sync', $property_settings);
|
||||
$field_config->save();
|
||||
|
||||
$this->contentTranslationManager = $this->container->get('content_translation.manager');
|
||||
$this->contentTranslationManager->setEnabled($entity_type_id, $entity_type_id, TRUE);
|
||||
|
||||
$this->storage = $this->entityTypeManager->getStorage($entity_type_id);
|
||||
|
||||
foreach ($this->getTestFiles('image') as $file) {
|
||||
$entity = File::create((array) $file + ['status' => 1]);
|
||||
$entity->save();
|
||||
}
|
||||
|
||||
$this->state->set('content_translation.entity_access.file', ['view' => TRUE]);
|
||||
|
||||
$account = User::create([
|
||||
'name' => $this->randomMachineName(),
|
||||
'status' => 1,
|
||||
]);
|
||||
$account->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that field synchronization works as expected with revisions.
|
||||
*
|
||||
* @covers \Drupal\content_translation\Plugin\Validation\Constraint\ContentTranslationSynchronizedFieldsConstraintValidator::create
|
||||
* @covers \Drupal\content_translation\Plugin\Validation\Constraint\ContentTranslationSynchronizedFieldsConstraintValidator::validate
|
||||
* @covers \Drupal\content_translation\Plugin\Validation\Constraint\ContentTranslationSynchronizedFieldsConstraintValidator::hasSynchronizedPropertyChanges
|
||||
* @covers \Drupal\content_translation\FieldTranslationSynchronizer::getFieldSynchronizedProperties
|
||||
* @covers \Drupal\content_translation\FieldTranslationSynchronizer::synchronizeFields
|
||||
* @covers \Drupal\content_translation\FieldTranslationSynchronizer::synchronizeItems
|
||||
*/
|
||||
public function testFieldSynchronizationAndValidation(): void {
|
||||
// Test that when untranslatable field widgets are displayed, synchronized
|
||||
// field properties can be changed only in default revisions.
|
||||
$this->setUntranslatableFieldWidgetsDisplay(TRUE);
|
||||
$entity = $this->saveNewEntity();
|
||||
$entity_id = $entity->id();
|
||||
$this->assertLatestRevisionFieldValues($entity_id, [1, 1, 1, 'Alt 1 EN']);
|
||||
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $en_revision */
|
||||
$en_revision = $this->createRevision($entity, FALSE);
|
||||
$en_revision->get($this->fieldName)->target_id = 2;
|
||||
$violations = $en_revision->validate();
|
||||
$this->assertViolations($violations);
|
||||
|
||||
$it_translation = $entity->addTranslation('it', $entity->toArray());
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $it_revision */
|
||||
$it_revision = $this->createRevision($it_translation, FALSE);
|
||||
$metadata = $this->contentTranslationManager->getTranslationMetadata($it_revision);
|
||||
$metadata->setSource('en');
|
||||
$it_revision->get($this->fieldName)->target_id = 2;
|
||||
$it_revision->get($this->fieldName)->alt = 'Alt 2 IT';
|
||||
$violations = $it_revision->validate();
|
||||
$this->assertViolations($violations);
|
||||
$it_revision->isDefaultRevision(TRUE);
|
||||
$violations = $it_revision->validate();
|
||||
$this->assertEmpty($violations);
|
||||
$this->storage->save($it_revision);
|
||||
$this->assertLatestRevisionFieldValues($entity_id, [2, 2, 2, 'Alt 1 EN', 'Alt 2 IT']);
|
||||
|
||||
$en_revision = $this->createRevision($en_revision, FALSE);
|
||||
$en_revision->get($this->fieldName)->alt = 'Alt 3 EN';
|
||||
$violations = $en_revision->validate();
|
||||
$this->assertEmpty($violations);
|
||||
$this->storage->save($en_revision);
|
||||
$this->assertLatestRevisionFieldValues($entity_id, [3, 2, 2, 'Alt 3 EN', 'Alt 2 IT']);
|
||||
|
||||
$it_revision = $this->createRevision($it_revision, FALSE);
|
||||
$it_revision->get($this->fieldName)->alt = 'Alt 4 IT';
|
||||
$violations = $it_revision->validate();
|
||||
$this->assertEmpty($violations);
|
||||
$this->storage->save($it_revision);
|
||||
$this->assertLatestRevisionFieldValues($entity_id, [4, 2, 2, 'Alt 1 EN', 'Alt 4 IT']);
|
||||
|
||||
$en_revision = $this->createRevision($en_revision);
|
||||
$en_revision->get($this->fieldName)->alt = 'Alt 5 EN';
|
||||
$violations = $en_revision->validate();
|
||||
$this->assertEmpty($violations);
|
||||
$this->storage->save($en_revision);
|
||||
$this->assertLatestRevisionFieldValues($entity_id, [5, 2, 2, 'Alt 5 EN', 'Alt 2 IT']);
|
||||
|
||||
$en_revision = $this->createRevision($en_revision);
|
||||
$en_revision->get($this->fieldName)->target_id = 6;
|
||||
$en_revision->get($this->fieldName)->alt = 'Alt 6 EN';
|
||||
$violations = $en_revision->validate();
|
||||
$this->assertEmpty($violations);
|
||||
$this->storage->save($en_revision);
|
||||
$this->assertLatestRevisionFieldValues($entity_id, [6, 6, 6, 'Alt 6 EN', 'Alt 2 IT']);
|
||||
|
||||
$it_revision = $this->createRevision($it_revision);
|
||||
$it_revision->get($this->fieldName)->alt = 'Alt 7 IT';
|
||||
$violations = $it_revision->validate();
|
||||
$this->assertEmpty($violations);
|
||||
$this->storage->save($it_revision);
|
||||
$this->assertLatestRevisionFieldValues($entity_id, [7, 6, 6, 'Alt 6 EN', 'Alt 7 IT']);
|
||||
|
||||
// Test that when untranslatable field widgets are hidden, synchronized
|
||||
// field properties can be changed only when editing the default
|
||||
// translation. This may lead to temporarily desynchronized values, when
|
||||
// saving a pending revision for the default translation that changes a
|
||||
// synchronized property (see revision 11).
|
||||
$this->setUntranslatableFieldWidgetsDisplay(FALSE);
|
||||
$entity = $this->saveNewEntity();
|
||||
$entity_id = $entity->id();
|
||||
$this->assertLatestRevisionFieldValues($entity_id, [8, 1, 1, 'Alt 1 EN']);
|
||||
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $en_revision */
|
||||
$en_revision = $this->createRevision($entity, FALSE);
|
||||
$en_revision->get($this->fieldName)->target_id = 2;
|
||||
$en_revision->get($this->fieldName)->alt = 'Alt 2 EN';
|
||||
$violations = $en_revision->validate();
|
||||
$this->assertEmpty($violations);
|
||||
$this->storage->save($en_revision);
|
||||
$this->assertLatestRevisionFieldValues($entity_id, [9, 2, 2, 'Alt 2 EN']);
|
||||
|
||||
$it_translation = $entity->addTranslation('it', $entity->toArray());
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $it_revision */
|
||||
$it_revision = $this->createRevision($it_translation, FALSE);
|
||||
$metadata = $this->contentTranslationManager->getTranslationMetadata($it_revision);
|
||||
$metadata->setSource('en');
|
||||
$it_revision->get($this->fieldName)->target_id = 3;
|
||||
$violations = $it_revision->validate();
|
||||
$this->assertViolations($violations);
|
||||
$it_revision->isDefaultRevision(TRUE);
|
||||
$violations = $it_revision->validate();
|
||||
$this->assertViolations($violations);
|
||||
|
||||
$it_revision = $this->createRevision($it_translation);
|
||||
$metadata = $this->contentTranslationManager->getTranslationMetadata($it_revision);
|
||||
$metadata->setSource('en');
|
||||
$it_revision->get($this->fieldName)->alt = 'Alt 3 IT';
|
||||
$violations = $it_revision->validate();
|
||||
$this->assertEmpty($violations);
|
||||
$this->storage->save($it_revision);
|
||||
$this->assertLatestRevisionFieldValues($entity_id, [10, 1, 1, 'Alt 1 EN', 'Alt 3 IT']);
|
||||
|
||||
$en_revision = $this->createRevision($en_revision, FALSE);
|
||||
$en_revision->get($this->fieldName)->alt = 'Alt 4 EN';
|
||||
$violations = $en_revision->validate();
|
||||
$this->assertEmpty($violations);
|
||||
$this->storage->save($en_revision);
|
||||
$this->assertLatestRevisionFieldValues($entity_id, [11, 2, 1, 'Alt 4 EN', 'Alt 3 IT']);
|
||||
|
||||
$it_revision = $this->createRevision($it_revision, FALSE);
|
||||
$it_revision->get($this->fieldName)->alt = 'Alt 5 IT';
|
||||
$violations = $it_revision->validate();
|
||||
$this->assertEmpty($violations);
|
||||
$this->storage->save($it_revision);
|
||||
$this->assertLatestRevisionFieldValues($entity_id, [12, 1, 1, 'Alt 1 EN', 'Alt 5 IT']);
|
||||
|
||||
$en_revision = $this->createRevision($en_revision);
|
||||
$en_revision->get($this->fieldName)->target_id = 6;
|
||||
$en_revision->get($this->fieldName)->alt = 'Alt 6 EN';
|
||||
$violations = $en_revision->validate();
|
||||
$this->assertEmpty($violations);
|
||||
$this->storage->save($en_revision);
|
||||
$this->assertLatestRevisionFieldValues($entity_id, [13, 6, 6, 'Alt 6 EN', 'Alt 3 IT']);
|
||||
|
||||
$it_revision = $this->createRevision($it_revision);
|
||||
$it_revision->get($this->fieldName)->target_id = 7;
|
||||
$violations = $it_revision->validate();
|
||||
$this->assertViolations($violations);
|
||||
|
||||
$it_revision = $this->createRevision($it_revision);
|
||||
$it_revision->get($this->fieldName)->alt = 'Alt 7 IT';
|
||||
$violations = $it_revision->validate();
|
||||
$this->assertEmpty($violations);
|
||||
$this->storage->save($it_revision);
|
||||
$this->assertLatestRevisionFieldValues($entity_id, [14, 6, 6, 'Alt 6 EN', 'Alt 7 IT']);
|
||||
|
||||
// Test that creating a default revision starting from a pending revision
|
||||
// having changes to synchronized properties, without introducing new
|
||||
// changes works properly.
|
||||
$this->setUntranslatableFieldWidgetsDisplay(FALSE);
|
||||
$entity = $this->saveNewEntity();
|
||||
$entity_id = $entity->id();
|
||||
$this->assertLatestRevisionFieldValues($entity_id, [15, 1, 1, 'Alt 1 EN']);
|
||||
|
||||
$it_translation = $entity->addTranslation('it', $entity->toArray());
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $it_revision */
|
||||
$it_revision = $this->createRevision($it_translation);
|
||||
$metadata = $this->contentTranslationManager->getTranslationMetadata($it_revision);
|
||||
$metadata->setSource('en');
|
||||
$it_revision->get($this->fieldName)->alt = 'Alt 2 IT';
|
||||
$violations = $it_revision->validate();
|
||||
$this->assertEmpty($violations);
|
||||
$this->storage->save($it_revision);
|
||||
$this->assertLatestRevisionFieldValues($entity_id, [16, 1, 1, 'Alt 1 EN', 'Alt 2 IT']);
|
||||
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $en_revision */
|
||||
$en_revision = $this->createRevision($entity);
|
||||
$en_revision->get($this->fieldName)->target_id = 3;
|
||||
$en_revision->get($this->fieldName)->alt = 'Alt 3 EN';
|
||||
$violations = $en_revision->validate();
|
||||
$this->assertEmpty($violations);
|
||||
$this->storage->save($en_revision);
|
||||
$this->assertLatestRevisionFieldValues($entity_id, [17, 3, 3, 'Alt 3 EN', 'Alt 2 IT']);
|
||||
|
||||
$en_revision = $this->createRevision($entity, FALSE);
|
||||
$en_revision->get($this->fieldName)->target_id = 4;
|
||||
$en_revision->get($this->fieldName)->alt = 'Alt 4 EN';
|
||||
$violations = $en_revision->validate();
|
||||
$this->assertEmpty($violations);
|
||||
$this->storage->save($en_revision);
|
||||
$this->assertLatestRevisionFieldValues($entity_id, [18, 4, 3, 'Alt 4 EN', 'Alt 2 IT']);
|
||||
|
||||
$en_revision = $this->createRevision($entity);
|
||||
$violations = $en_revision->validate();
|
||||
$this->assertEmpty($violations);
|
||||
$this->storage->save($en_revision);
|
||||
$this->assertLatestRevisionFieldValues($entity_id, [19, 4, 4, 'Alt 4 EN', 'Alt 2 IT']);
|
||||
|
||||
$it_revision = $this->createRevision($it_revision);
|
||||
$it_revision->get($this->fieldName)->alt = 'Alt 6 IT';
|
||||
$violations = $it_revision->validate();
|
||||
$this->assertEmpty($violations);
|
||||
$this->storage->save($it_revision);
|
||||
$this->assertLatestRevisionFieldValues($entity_id, [20, 4, 4, 'Alt 4 EN', 'Alt 6 IT']);
|
||||
|
||||
// Check that we are not allowed to perform changes to multiple translations
|
||||
// in pending revisions when synchronized properties are involved.
|
||||
$this->setUntranslatableFieldWidgetsDisplay(FALSE);
|
||||
$entity = $this->saveNewEntity();
|
||||
$entity_id = $entity->id();
|
||||
$this->assertLatestRevisionFieldValues($entity_id, [21, 1, 1, 'Alt 1 EN']);
|
||||
|
||||
$it_translation = $entity->addTranslation('it', $entity->toArray());
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $it_revision */
|
||||
$it_revision = $this->createRevision($it_translation);
|
||||
$metadata = $this->contentTranslationManager->getTranslationMetadata($it_revision);
|
||||
$metadata->setSource('en');
|
||||
$it_revision->get($this->fieldName)->alt = 'Alt 2 IT';
|
||||
$violations = $it_revision->validate();
|
||||
$this->assertEmpty($violations);
|
||||
$this->storage->save($it_revision);
|
||||
$this->assertLatestRevisionFieldValues($entity_id, [22, 1, 1, 'Alt 1 EN', 'Alt 2 IT']);
|
||||
|
||||
$en_revision = $this->createRevision($entity, FALSE);
|
||||
$en_revision->get($this->fieldName)->target_id = 2;
|
||||
$en_revision->getTranslation('it')->get($this->fieldName)->alt = 'Alt 3 IT';
|
||||
$violations = $en_revision->validate();
|
||||
$this->assertViolations($violations);
|
||||
|
||||
// Test that when saving a new default revision starting from a pending
|
||||
// revision, outdated synchronized properties do not override more recent
|
||||
// ones.
|
||||
$this->setUntranslatableFieldWidgetsDisplay(TRUE);
|
||||
$entity = $this->saveNewEntity();
|
||||
$entity_id = $entity->id();
|
||||
$this->assertLatestRevisionFieldValues($entity_id, [23, 1, 1, 'Alt 1 EN']);
|
||||
|
||||
$it_translation = $entity->addTranslation('it', $entity->toArray());
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $it_revision */
|
||||
$it_revision = $this->createRevision($it_translation, FALSE);
|
||||
$metadata = $this->contentTranslationManager->getTranslationMetadata($it_revision);
|
||||
$metadata->setSource('en');
|
||||
$it_revision->get($this->fieldName)->alt = 'Alt 2 IT';
|
||||
$violations = $it_revision->validate();
|
||||
$this->assertEmpty($violations);
|
||||
$this->storage->save($it_revision);
|
||||
$this->assertLatestRevisionFieldValues($entity_id, [24, 1, 1, 'Alt 1 EN', 'Alt 2 IT']);
|
||||
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $en_revision */
|
||||
$en_revision = $this->createRevision($entity);
|
||||
$en_revision->get($this->fieldName)->target_id = 3;
|
||||
$en_revision->get($this->fieldName)->alt = 'Alt 3 EN';
|
||||
$violations = $en_revision->validate();
|
||||
$this->assertEmpty($violations);
|
||||
$this->storage->save($en_revision);
|
||||
$this->assertLatestRevisionFieldValues($entity_id, [25, 3, 3, 'Alt 3 EN', 'Alt 2 IT']);
|
||||
|
||||
$it_revision = $this->createRevision($it_revision);
|
||||
$it_revision->get($this->fieldName)->alt = 'Alt 4 IT';
|
||||
$violations = $it_revision->validate();
|
||||
$this->assertEmpty($violations);
|
||||
$this->storage->save($it_revision);
|
||||
$this->assertLatestRevisionFieldValues($entity_id, [26, 3, 3, 'Alt 3 EN', 'Alt 4 IT']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that file field synchronization works as expected.
|
||||
*/
|
||||
public function testFileFieldSynchronization(): void {
|
||||
$entity_type_id = 'entity_test_mulrev';
|
||||
$file_field_name = 'file_field';
|
||||
|
||||
foreach ($this->getTestFiles('text') as $file) {
|
||||
$entity = File::create((array) $file + ['status' => 1]);
|
||||
$entity->save();
|
||||
}
|
||||
|
||||
/** @var \Drupal\field\Entity\FieldStorageConfig $field_storage */
|
||||
$field_storage_config = FieldStorageConfig::create([
|
||||
'field_name' => $file_field_name,
|
||||
'type' => 'file',
|
||||
'entity_type' => $entity_type_id,
|
||||
'cardinality' => 1,
|
||||
'translatable' => 1,
|
||||
]);
|
||||
$field_storage_config->save();
|
||||
|
||||
$field_config = FieldConfig::create([
|
||||
'entity_type' => $entity_type_id,
|
||||
'field_name' => $file_field_name,
|
||||
'bundle' => $entity_type_id,
|
||||
'label' => 'Synchronized file field',
|
||||
'translatable' => 1,
|
||||
]);
|
||||
$field_config->save();
|
||||
|
||||
$property_settings = [
|
||||
'display' => 'display',
|
||||
'description' => 'description',
|
||||
'target_id' => 0,
|
||||
];
|
||||
$field_config->setThirdPartySetting('content_translation', 'translation_sync', $property_settings);
|
||||
$field_config->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
$entity = EntityTestMulRev::create([
|
||||
'uid' => 1,
|
||||
'langcode' => 'en',
|
||||
$file_field_name => [
|
||||
'target_id' => 1,
|
||||
'description' => 'Description EN',
|
||||
'display' => 1,
|
||||
],
|
||||
]);
|
||||
$entity->save();
|
||||
|
||||
$this->assertEquals(1, $entity->get($file_field_name)->target_id);
|
||||
$this->assertEquals('Description EN', $entity->get($file_field_name)->description);
|
||||
$this->assertEquals(1, $entity->get($file_field_name)->display);
|
||||
|
||||
// Create a translation with a different file, description and display
|
||||
// values.
|
||||
$it_translation = $entity->addTranslation('it', $entity->toArray());
|
||||
$it_translation->get($file_field_name)->target_id = 2;
|
||||
$it_translation->get($file_field_name)->description = 'Description IT';
|
||||
$it_translation->get($file_field_name)->display = 0;
|
||||
$metadata = $this->contentTranslationManager->getTranslationMetadata($it_translation);
|
||||
$metadata->setSource('en');
|
||||
$it_translation->save();
|
||||
|
||||
$it_entity = $entity->getTranslation('it');
|
||||
$this->assertEquals(2, $it_entity->get($file_field_name)->target_id);
|
||||
$this->assertEquals('Description IT', $it_entity->get($file_field_name)->description);
|
||||
$this->assertEquals(0, $it_entity->get($file_field_name)->display);
|
||||
|
||||
// In the english entity the file should have changed, but the description
|
||||
// and display should have remained the same.
|
||||
$en_entity = $entity->getTranslation('en');
|
||||
$this->assertEquals(2, $en_entity->get($file_field_name)->target_id);
|
||||
$this->assertEquals('Description EN', $en_entity->get($file_field_name)->description);
|
||||
$this->assertEquals(1, $en_entity->get($file_field_name)->display);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests changing the default language of an entity.
|
||||
*/
|
||||
public function testChangeDefaultLanguageNonTranslatableFieldsHidden(): void {
|
||||
$this->setUntranslatableFieldWidgetsDisplay(FALSE);
|
||||
$entity = $this->saveNewEntity();
|
||||
$entity->langcode = 'it';
|
||||
$this->assertCount(0, $entity->validate());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets untranslatable field widgets' display status.
|
||||
*
|
||||
* @param bool $display
|
||||
* Whether untranslatable field widgets should be displayed.
|
||||
*/
|
||||
protected function setUntranslatableFieldWidgetsDisplay($display): void {
|
||||
$entity_type_id = $this->storage->getEntityTypeId();
|
||||
$settings = ['untranslatable_fields_hide' => !$display];
|
||||
$this->contentTranslationManager->setBundleTranslationSettings($entity_type_id, $entity_type_id, $settings);
|
||||
/** @var \Drupal\Core\Entity\EntityTypeBundleInfo $bundle_info */
|
||||
$bundle_info = $this->container->get('entity_type.bundle.info');
|
||||
$bundle_info->clearCachedBundles();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Drupal\Core\Entity\ContentEntityInterface
|
||||
* The saved entity.
|
||||
*/
|
||||
protected function saveNewEntity() {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
$entity = EntityTestMulRev::create([
|
||||
'uid' => 1,
|
||||
'langcode' => 'en',
|
||||
$this->fieldName => [
|
||||
'target_id' => 1,
|
||||
'alt' => 'Alt 1 EN',
|
||||
],
|
||||
]);
|
||||
$metadata = $this->contentTranslationManager->getTranslationMetadata($entity);
|
||||
$metadata->setSource(LanguageInterface::LANGCODE_NOT_SPECIFIED);
|
||||
$violations = $entity->validate();
|
||||
$this->assertEmpty($violations);
|
||||
$this->storage->save($entity);
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new revision starting from the latest translation-affecting one.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $translation
|
||||
* The translation to be revisioned.
|
||||
* @param bool $default
|
||||
* (optional) Whether the new revision should be marked as default. Defaults
|
||||
* to TRUE.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\ContentEntityInterface
|
||||
* An entity revision object.
|
||||
*/
|
||||
protected function createRevision(ContentEntityInterface $translation, $default = TRUE) {
|
||||
if (!$translation->isNewTranslation()) {
|
||||
$langcode = $translation->language()->getId();
|
||||
$revision_id = $this->storage->getLatestTranslationAffectedRevisionId($translation->id(), $langcode);
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $revision */
|
||||
$revision = $this->storage->loadRevision($revision_id);
|
||||
$translation = $revision->getTranslation($langcode);
|
||||
}
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $revision */
|
||||
$revision = $this->storage->createRevision($translation, $default);
|
||||
return $revision;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the expected violations were found.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityConstraintViolationListInterface $violations
|
||||
* A list of violations.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertViolations(EntityConstraintViolationListInterface $violations): void {
|
||||
$entity_type_id = $this->storage->getEntityTypeId();
|
||||
$settings = $this->contentTranslationManager->getBundleTranslationSettings($entity_type_id, $entity_type_id);
|
||||
$message = !empty($settings['untranslatable_fields_hide']) ?
|
||||
'Non-translatable field elements can only be changed when updating the original language.' :
|
||||
'Non-translatable field elements can only be changed when updating the current revision.';
|
||||
|
||||
$list = [];
|
||||
foreach ($violations as $violation) {
|
||||
if ((string) $violation->getMessage() === $message) {
|
||||
$list[] = $violation;
|
||||
}
|
||||
}
|
||||
$this->assertCount(1, $list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the latest revision has the expected field values.
|
||||
*
|
||||
* @param string $entity_id
|
||||
* The entity ID.
|
||||
* @param array $expected_values
|
||||
* An array of expected values in the following order:
|
||||
* - revision ID
|
||||
* - target ID (en)
|
||||
* - target ID (it)
|
||||
* - alt (en)
|
||||
* - alt (it)
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertLatestRevisionFieldValues(string $entity_id, array $expected_values): void {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
$entity = $this->storage->loadRevision($this->storage->getLatestRevisionId($entity_id));
|
||||
@[$revision_id, $target_id_en, $target_id_it, $alt_en, $alt_it] = $expected_values;
|
||||
$this->assertEquals($revision_id, $entity->getRevisionId());
|
||||
$this->assertEquals($target_id_en, $entity->get($this->fieldName)->target_id);
|
||||
$this->assertEquals($alt_en, $entity->get($this->fieldName)->alt);
|
||||
if ($entity->hasTranslation('it')) {
|
||||
$it_translation = $entity->getTranslation('it');
|
||||
$this->assertEquals($target_id_it, $it_translation->get($this->fieldName)->target_id);
|
||||
$this->assertEquals($alt_it, $it_translation->get($this->fieldName)->alt);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,235 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Kernel;
|
||||
|
||||
use Drupal\Core\Form\FormState;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
|
||||
/**
|
||||
* Tests the content translation handler.
|
||||
*
|
||||
* @group content_translation
|
||||
*
|
||||
* @coversDefaultClass \Drupal\content_translation\ContentTranslationHandler
|
||||
*/
|
||||
class ContentTranslationHandlerTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'content_translation',
|
||||
'entity_test',
|
||||
'language',
|
||||
'user',
|
||||
];
|
||||
|
||||
/**
|
||||
* The state service.
|
||||
*
|
||||
* @var \Drupal\Core\State\StateInterface
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* The entity type bundle information.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
|
||||
*/
|
||||
protected $entityTypeBundleInfo;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The messenger.
|
||||
*
|
||||
* @var \Drupal\Core\Messenger\MessengerInterface
|
||||
*/
|
||||
protected $messenger;
|
||||
|
||||
/**
|
||||
* The ID of the entity type used in this test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $entityTypeId = 'entity_test_mul';
|
||||
|
||||
/**
|
||||
* The ID of the translation language used in this test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $translationLangcode = 'af';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->state = $this->container->get('state');
|
||||
$this->entityTypeBundleInfo = $this->container->get('entity_type.bundle.info');
|
||||
$this->entityTypeManager = $this->container->get('entity_type.manager');
|
||||
$this->messenger = $this->container->get('messenger');
|
||||
|
||||
$this->installEntitySchema($this->entityTypeId);
|
||||
ConfigurableLanguage::createFromLangcode($this->translationLangcode)->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests ContentTranslationHandler::entityFormSharedElements()
|
||||
*
|
||||
* @param array $element
|
||||
* The element that will be altered.
|
||||
* @param bool $default_translation_affected
|
||||
* Whether or not only the default translation of the entity is affected.
|
||||
* @param bool $default_translation
|
||||
* Whether or not the entity is the default translation.
|
||||
* @param bool $translation_form
|
||||
* Whether or not the form is a translation form.
|
||||
* @param array $expected
|
||||
* The expected altered element.
|
||||
*
|
||||
* @dataProvider providerTestEntityFormSharedElements
|
||||
*
|
||||
* @covers ::entityFormSharedElements
|
||||
* @covers ::addTranslatabilityClue
|
||||
*/
|
||||
public function testEntityFormSharedElements(array $element, $default_translation_affected, $default_translation, $translation_form, array $expected): void {
|
||||
$this->state->set('entity_test.translation', TRUE);
|
||||
$this->state->set('entity_test.untranslatable_fields.default_translation_affected', $default_translation_affected);
|
||||
$this->entityTypeBundleInfo->clearCachedBundles();
|
||||
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
$entity = $this->entityTypeManager->getStorage($this->entityTypeId)->create();
|
||||
if (!$default_translation) {
|
||||
$entity = $entity->addTranslation($this->translationLangcode);
|
||||
}
|
||||
$entity->save();
|
||||
|
||||
$form_object = $this->entityTypeManager->getFormObject($this->entityTypeId, 'default');
|
||||
$form_object->setEntity($entity);
|
||||
|
||||
$form_state = new FormState();
|
||||
$form_state
|
||||
->addBuildInfo('callback_object', $form_object)
|
||||
->set(['content_translation', 'translation_form'], $translation_form);
|
||||
|
||||
$handler = $this->entityTypeManager->getHandler($this->entityTypeId, 'translation');
|
||||
$actual = $handler->entityFormSharedElements($element, $form_state, $element);
|
||||
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns test cases for ::testEntityFormSharedElements().
|
||||
*
|
||||
* @return array[]
|
||||
* An array of test cases, each one containing the element to alter, the
|
||||
* form state, and the expected altered element.
|
||||
*/
|
||||
public static function providerTestEntityFormSharedElements() {
|
||||
$tests = [];
|
||||
|
||||
$element = [];
|
||||
$tests['empty'] = [
|
||||
'element' => $element,
|
||||
'default_translation_affected' => TRUE,
|
||||
'default_translation' => TRUE,
|
||||
'translation_form' => FALSE,
|
||||
'expected' => $element,
|
||||
];
|
||||
|
||||
$element = [
|
||||
'#type' => 'textfield',
|
||||
];
|
||||
$tests['no-children'] = $tests['empty'];
|
||||
$tests['no-children']['element'] = $element;
|
||||
$tests['no-children']['expected'] = $element;
|
||||
|
||||
$element = [
|
||||
'test' => [
|
||||
'#type' => 'textfield',
|
||||
'#multilingual' => TRUE,
|
||||
],
|
||||
];
|
||||
$tests['multilingual'] = $tests['empty'];
|
||||
$tests['multilingual']['element'] = $element;
|
||||
$tests['multilingual']['expected'] = $element;
|
||||
|
||||
unset($element['test']['#multilingual']);
|
||||
$tests['no-title'] = $tests['empty'];
|
||||
$tests['no-title']['element'] = $element;
|
||||
$tests['no-title']['expected'] = $element;
|
||||
|
||||
$element['test']['#title'] = 'Test';
|
||||
$tests['no-translatability-clue'] = $tests['empty'];
|
||||
$tests['no-translatability-clue']['element'] = $element;
|
||||
$tests['no-translatability-clue']['expected'] = $element;
|
||||
|
||||
$expected = $element;
|
||||
$expected['test']['#title'] .= ' <span class="translation-entity-all-languages">(all languages)</span>';
|
||||
$tests['translatability-clue'] = $tests['no-translatability-clue'];
|
||||
$tests['translatability-clue']['default_translation_affected'] = FALSE;
|
||||
$tests['translatability-clue']['expected'] = $expected;
|
||||
|
||||
$ignored_types = [
|
||||
'actions',
|
||||
'details',
|
||||
'hidden',
|
||||
'link',
|
||||
'token',
|
||||
'value',
|
||||
'vertical_tabs',
|
||||
];
|
||||
foreach ($ignored_types as $ignored_type) {
|
||||
$element = [
|
||||
'test' => [
|
||||
'#type' => $ignored_type,
|
||||
'#title' => 'Test',
|
||||
],
|
||||
];
|
||||
$tests["ignore-$ignored_type"] = $tests['translatability-clue'];
|
||||
$tests["ignore-$ignored_type"]['element'] = $element;
|
||||
$tests["ignore-$ignored_type"]['expected'] = $element;
|
||||
}
|
||||
|
||||
$tests['unknown-field'] = $tests['no-translatability-clue'];
|
||||
$tests['unknown-field']['default_translation'] = FALSE;
|
||||
|
||||
$element = [
|
||||
'name' => [
|
||||
'#type' => 'textfield',
|
||||
],
|
||||
'hidden_fields_warning_message' => [
|
||||
'#theme' => 'status_messages',
|
||||
'#message_list' => [
|
||||
'warning' => [t('Fields that apply to all languages are hidden to avoid conflicting changes. <a href=":url">Edit them on the original language form</a>.')],
|
||||
],
|
||||
'#weight' => -100,
|
||||
'#status_headings' => [
|
||||
'warning' => t('Warning message'),
|
||||
],
|
||||
],
|
||||
];
|
||||
$expected = $element;
|
||||
$expected['name']['#access'] = FALSE;
|
||||
$tests['hide-untranslatable'] = $tests['unknown-field'];
|
||||
$tests['hide-untranslatable']['element'] = $element;
|
||||
$tests['hide-untranslatable']['expected'] = $expected;
|
||||
|
||||
$tests['no-translation-form'] = $tests['no-translatability-clue'];
|
||||
$tests['no-translation-form']['translation_form'] = FALSE;
|
||||
|
||||
return $tests;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Kernel;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTestWithBundle;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
|
||||
/**
|
||||
* Tests content translation for modules that provide translatable bundles.
|
||||
*
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationModuleInstallTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'content_translation',
|
||||
'content_translation_test',
|
||||
'entity_test',
|
||||
'language',
|
||||
'user',
|
||||
];
|
||||
|
||||
/**
|
||||
* The content translation manager.
|
||||
*
|
||||
* @var \Drupal\content_translation\ContentTranslationManagerInterface
|
||||
*/
|
||||
protected $contentTranslationManager;
|
||||
|
||||
/**
|
||||
* The language code of the source language for this test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $sourceLangcode = 'en';
|
||||
|
||||
/**
|
||||
* The language code of the translation language for this test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $translationLangcode = 'af';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('entity_test_with_bundle');
|
||||
ConfigurableLanguage::createFromLangcode($this->translationLangcode)->save();
|
||||
|
||||
$this->contentTranslationManager = $this->container->get('content_translation.manager');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that content translation fields are created upon module installation.
|
||||
*/
|
||||
public function testFieldUpdates(): void {
|
||||
// The module ships a translatable bundle of the 'entity_test_with_bundle'
|
||||
// entity type.
|
||||
$this->installConfig(['content_translation_test']);
|
||||
|
||||
$entity = EntityTestWithBundle::create([
|
||||
'type' => 'test',
|
||||
'langcode' => $this->sourceLangcode,
|
||||
]);
|
||||
$entity->save();
|
||||
|
||||
// Add a translation with some translation metadata.
|
||||
$translation = $entity->addTranslation($this->translationLangcode);
|
||||
$translation_metadata = $this->contentTranslationManager->getTranslationMetadata($translation);
|
||||
$translation_metadata->setSource($this->sourceLangcode)->setOutdated(TRUE);
|
||||
$translation->save();
|
||||
|
||||
// Make sure the translation metadata has been saved correctly.
|
||||
$entity = EntityTestWithBundle::load($entity->id());
|
||||
$translation = $entity->getTranslation($this->translationLangcode);
|
||||
$translation_metadata = $this->contentTranslationManager->getTranslationMetadata($translation);
|
||||
$this->assertSame($this->sourceLangcode, $translation_metadata->getSource());
|
||||
$this->assertTrue($translation_metadata->isOutdated());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Kernel;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTestMulBundle;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests the content translation dynamic permissions.
|
||||
*
|
||||
* @group content_translation
|
||||
*
|
||||
* @coversDefaultClass \Drupal\content_translation\ContentTranslationPermissions
|
||||
*/
|
||||
class ContentTranslationPermissionsTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['system', 'language', 'content_translation', 'user', 'entity_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('entity_test_mul');
|
||||
$this->installEntitySchema('entity_test_mul_with_bundle');
|
||||
EntityTestMulBundle::create([
|
||||
'id' => 'test',
|
||||
'label' => 'Test label',
|
||||
'description' => 'My test description',
|
||||
])->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that enabling translation via the API triggers schema updates.
|
||||
*/
|
||||
public function testPermissions(): void {
|
||||
$this->container->get('content_translation.manager')->setEnabled('entity_test_mul', 'entity_test_mul', TRUE);
|
||||
$this->container->get('content_translation.manager')->setEnabled('entity_test_mul_with_bundle', 'test', TRUE);
|
||||
$permissions = $this->container->get('user.permissions')->getPermissions();
|
||||
$this->assertEquals(['entity_test'], $permissions['translate entity_test_mul']['dependencies']['module']);
|
||||
$this->assertEquals(['entity_test.entity_test_mul_bundle.test'], $permissions['translate test entity_test_mul_with_bundle']['dependencies']['config']);
|
||||
|
||||
// Ensure bundle permission granularity works for bundles not based on
|
||||
// configuration.
|
||||
$this->container->get('state')->set('entity_test_mul.permission_granularity', 'bundle');
|
||||
$this->container->get('entity_type.manager')->clearCachedDefinitions();
|
||||
$permissions = $this->container->get('user.permissions')->getPermissions();
|
||||
$this->assertEquals(['entity_test'], $permissions['translate entity_test_mul entity_test_mul']['dependencies']['module']);
|
||||
$this->assertEquals(['entity_test.entity_test_mul_bundle.test'], $permissions['translate test entity_test_mul_with_bundle']['dependencies']['config']);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Kernel;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests the content translation settings API.
|
||||
*
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationSettingsApiTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'language',
|
||||
'content_translation',
|
||||
'user',
|
||||
'entity_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('entity_test_mul');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that enabling translation via the API triggers schema updates.
|
||||
*/
|
||||
public function testSettingsApi(): void {
|
||||
$this->container->get('content_translation.manager')->setEnabled('entity_test_mul', 'entity_test_mul', TRUE);
|
||||
$schema = Database::getConnection()->schema();
|
||||
$result =
|
||||
$schema->fieldExists('entity_test_mul_property_data', 'content_translation_source') &&
|
||||
$schema->fieldExists('entity_test_mul_property_data', 'content_translation_outdated');
|
||||
$this->assertTrue($result, 'Schema updates correctly performed.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,268 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Kernel;
|
||||
|
||||
use Drupal\content_translation\FieldTranslationSynchronizer;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests the field synchronization logic.
|
||||
*
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationSyncUnitTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* The synchronizer class to be tested.
|
||||
*
|
||||
* @var \Drupal\content_translation\FieldTranslationSynchronizer
|
||||
*/
|
||||
protected $synchronizer;
|
||||
|
||||
/**
|
||||
* The columns to be synchronized.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $synchronized;
|
||||
|
||||
/**
|
||||
* All the field columns.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $columns;
|
||||
|
||||
/**
|
||||
* The available language codes.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $langcodes;
|
||||
|
||||
/**
|
||||
* The field cardinality.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $cardinality;
|
||||
|
||||
/**
|
||||
* The unchanged field values.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $unchangedFieldValues;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['language', 'content_translation'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->synchronizer = new FieldTranslationSynchronizer($this->container->get('entity_type.manager'), $this->container->get('plugin.manager.field.field_type'));
|
||||
$this->synchronized = ['sync1', 'sync2'];
|
||||
$this->columns = array_merge($this->synchronized, ['var1', 'var2']);
|
||||
$this->langcodes = ['en', 'it', 'fr', 'de', 'es'];
|
||||
$this->cardinality = 4;
|
||||
$this->unchangedFieldValues = [];
|
||||
|
||||
// Set up an initial set of values in the correct state, that is with
|
||||
// "synchronized" values being equal.
|
||||
foreach ($this->langcodes as $langcode) {
|
||||
for ($delta = 0; $delta < $this->cardinality; $delta++) {
|
||||
foreach ($this->columns as $column) {
|
||||
$sync = in_array($column, $this->synchronized) && $langcode != $this->langcodes[0];
|
||||
$value = $sync ? $this->unchangedFieldValues[$this->langcodes[0]][$delta][$column] : $langcode . '-' . $delta . '-' . $column;
|
||||
$this->unchangedFieldValues[$langcode][$delta][$column] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the field synchronization algorithm.
|
||||
*/
|
||||
public function testFieldSync(): void {
|
||||
// Add a new item to the source items and check that its added to all the
|
||||
// translations.
|
||||
$sync_langcode = $this->langcodes[2];
|
||||
$unchanged_items = $this->unchangedFieldValues[$sync_langcode];
|
||||
$field_values = $this->unchangedFieldValues;
|
||||
$item = [];
|
||||
foreach ($this->columns as $column) {
|
||||
$item[$column] = $this->randomMachineName();
|
||||
}
|
||||
$field_values[$sync_langcode][] = $item;
|
||||
$this->synchronizer->synchronizeItems($field_values, $unchanged_items, $sync_langcode, $this->langcodes, $this->synchronized);
|
||||
$result = TRUE;
|
||||
foreach ($this->unchangedFieldValues as $langcode => $items) {
|
||||
// Check that the old values are still in place.
|
||||
for ($delta = 0; $delta < $this->cardinality; $delta++) {
|
||||
foreach ($this->columns as $column) {
|
||||
$result = $result && ($this->unchangedFieldValues[$langcode][$delta][$column] == $field_values[$langcode][$delta][$column]);
|
||||
}
|
||||
}
|
||||
// Check that the new item is available in all languages.
|
||||
foreach ($this->columns as $column) {
|
||||
$result = $result && ($field_values[$langcode][$delta][$column] == $field_values[$sync_langcode][$delta][$column]);
|
||||
}
|
||||
}
|
||||
$this->assertTrue($result, 'A new item has been correctly synchronized.');
|
||||
|
||||
// Remove an item from the source items and check that its removed from all
|
||||
// the translations.
|
||||
$sync_langcode = $this->langcodes[1];
|
||||
$unchanged_items = $this->unchangedFieldValues[$sync_langcode];
|
||||
$field_values = $this->unchangedFieldValues;
|
||||
$sync_delta = mt_rand(0, count($field_values[$sync_langcode]) - 1);
|
||||
unset($field_values[$sync_langcode][$sync_delta]);
|
||||
// Renumber deltas to start from 0.
|
||||
$field_values[$sync_langcode] = array_values($field_values[$sync_langcode]);
|
||||
$this->synchronizer->synchronizeItems($field_values, $unchanged_items, $sync_langcode, $this->langcodes, $this->synchronized);
|
||||
$result = TRUE;
|
||||
foreach ($this->unchangedFieldValues as $langcode => $items) {
|
||||
$new_delta = 0;
|
||||
// Check that the old values are still in place.
|
||||
for ($delta = 0; $delta < $this->cardinality; $delta++) {
|
||||
// Skip the removed item.
|
||||
if ($delta != $sync_delta) {
|
||||
foreach ($this->columns as $column) {
|
||||
$result = $result && ($this->unchangedFieldValues[$langcode][$delta][$column] == $field_values[$langcode][$new_delta][$column]);
|
||||
}
|
||||
$new_delta++;
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->assertTrue($result, 'A removed item has been correctly synchronized.');
|
||||
|
||||
// Move the items around in the source items and check that they are moved
|
||||
// in all the translations.
|
||||
$sync_langcode = $this->langcodes[3];
|
||||
$unchanged_items = $this->unchangedFieldValues[$sync_langcode];
|
||||
$field_values = $this->unchangedFieldValues;
|
||||
$field_values[$sync_langcode] = [];
|
||||
// Scramble the items.
|
||||
foreach ($unchanged_items as $delta => $item) {
|
||||
$new_delta = ($delta + 1) % $this->cardinality;
|
||||
$field_values[$sync_langcode][$new_delta] = $item;
|
||||
}
|
||||
// Renumber deltas to start from 0.
|
||||
ksort($field_values[$sync_langcode]);
|
||||
$this->synchronizer->synchronizeItems($field_values, $unchanged_items, $sync_langcode, $this->langcodes, $this->synchronized);
|
||||
$result = TRUE;
|
||||
foreach ($field_values as $langcode => $items) {
|
||||
for ($delta = 0; $delta < $this->cardinality; $delta++) {
|
||||
foreach ($this->columns as $column) {
|
||||
$value = $field_values[$langcode][$delta][$column];
|
||||
if (in_array($column, $this->synchronized)) {
|
||||
// If we are dealing with a synchronize column the current value is
|
||||
// supposed to be the same of the source items.
|
||||
$result = $result && $field_values[$sync_langcode][$delta][$column] == $value;
|
||||
}
|
||||
else {
|
||||
// Otherwise the values should be unchanged.
|
||||
$old_delta = ($delta > 0 ? $delta : $this->cardinality) - 1;
|
||||
$result = $result && $this->unchangedFieldValues[$langcode][$old_delta][$column] == $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->assertTrue($result, 'Scrambled items have been correctly synchronized.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that items holding the same values are correctly synchronized.
|
||||
*/
|
||||
public function testMultipleSyncedValues(): void {
|
||||
$sync_langcode = $this->langcodes[1];
|
||||
$unchanged_items = $this->unchangedFieldValues[$sync_langcode];
|
||||
|
||||
// Determine whether the unchanged values should be altered depending on
|
||||
// their delta.
|
||||
$delta_callbacks = [
|
||||
// Continuous field values: all values are equal.
|
||||
function ($delta) {
|
||||
return TRUE;
|
||||
},
|
||||
// Alternated field values: only the even ones are equal.
|
||||
function ($delta) {
|
||||
return $delta % 2 !== 0;
|
||||
},
|
||||
// Sparse field values: only the "middle" ones are equal.
|
||||
function ($delta) {
|
||||
return $delta === 1 || $delta === 2;
|
||||
},
|
||||
// Sparse field values: only the "extreme" ones are equal.
|
||||
function ($delta) {
|
||||
return $delta === 0 || $delta === 3;
|
||||
},
|
||||
];
|
||||
|
||||
foreach ($delta_callbacks as $delta_callback) {
|
||||
$field_values = $this->unchangedFieldValues;
|
||||
|
||||
for ($delta = 0; $delta < $this->cardinality; $delta++) {
|
||||
if ($delta_callback($delta)) {
|
||||
foreach ($this->columns as $column) {
|
||||
if (in_array($column, $this->synchronized)) {
|
||||
$field_values[$sync_langcode][$delta][$column] = $field_values[$sync_langcode][0][$column];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$changed_items = $field_values[$sync_langcode];
|
||||
$this->synchronizer->synchronizeItems($field_values, $unchanged_items, $sync_langcode, $this->langcodes, $this->synchronized);
|
||||
|
||||
foreach ($this->unchangedFieldValues as $langcode => $unchanged_items) {
|
||||
for ($delta = 0; $delta < $this->cardinality; $delta++) {
|
||||
foreach ($this->columns as $column) {
|
||||
// The first item is always unchanged hence it is retained by the
|
||||
// synchronization process. The other ones are retained or synced
|
||||
// depending on the logic implemented by the delta callback and
|
||||
// whether it is a sync column or not.
|
||||
$value = $delta > 0 && $delta_callback($delta) && in_array($column, $this->synchronized) ? $changed_items[0][$column] : $unchanged_items[$delta][$column];
|
||||
$this->assertEquals($field_values[$langcode][$delta][$column], $value, "Item $delta column $column for langcode $langcode synced correctly");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that one change in a synchronized column triggers a change in all columns.
|
||||
*/
|
||||
public function testDifferingSyncedColumns(): void {
|
||||
$sync_langcode = $this->langcodes[2];
|
||||
$unchanged_items = $this->unchangedFieldValues[$sync_langcode];
|
||||
$field_values = $this->unchangedFieldValues;
|
||||
|
||||
for ($delta = 0; $delta < $this->cardinality; $delta++) {
|
||||
$index = ($delta % 2) + 1;
|
||||
$field_values[$sync_langcode][$delta]['sync' . $index] .= '-updated';
|
||||
}
|
||||
|
||||
$changed_items = $field_values[$sync_langcode];
|
||||
$this->synchronizer->synchronizeItems($field_values, $unchanged_items, $sync_langcode, $this->langcodes, $this->synchronized);
|
||||
|
||||
foreach ($this->unchangedFieldValues as $langcode => $unchanged_items) {
|
||||
for ($delta = 0; $delta < $this->cardinality; $delta++) {
|
||||
foreach ($this->columns as $column) {
|
||||
// If the column is synchronized, the value should have been synced,
|
||||
// for columns that are not synchronized, the value must not change.
|
||||
$expected_value = in_array($column, $this->synchronized) ? $changed_items[$delta][$column] : $this->unchangedFieldValues[$langcode][$delta][$column];
|
||||
$this->assertEquals($expected_value, $field_values[$langcode][$delta][$column], "Differing Item {$delta} column {$column} for langcode {$langcode} synced correctly");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,200 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Kernel\Migrate\d6;
|
||||
|
||||
use Drupal\taxonomy\Entity\Term;
|
||||
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
|
||||
use Drupal\taxonomy\TermInterface;
|
||||
|
||||
/**
|
||||
* Test migration of translated taxonomy terms.
|
||||
*
|
||||
* @group migrate_drupal_6
|
||||
*/
|
||||
class MigrateTaxonomyTermTranslationTest extends MigrateDrupal6TestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'content_translation',
|
||||
'language',
|
||||
'menu_ui',
|
||||
'node',
|
||||
'taxonomy',
|
||||
];
|
||||
|
||||
/**
|
||||
* The cached taxonomy tree items, keyed by vid and tid.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $treeData = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('taxonomy_term');
|
||||
$this->installConfig(static::$modules);
|
||||
$this->executeMigrations([
|
||||
'language',
|
||||
'd6_node_type',
|
||||
'd6_field',
|
||||
'd6_taxonomy_vocabulary',
|
||||
'd6_field_instance',
|
||||
'd6_taxonomy_term',
|
||||
'd6_taxonomy_term_translation',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a migrated term contains the expected values.
|
||||
*
|
||||
* @param int $id
|
||||
* Entity ID to load and check.
|
||||
* @param string $expected_language
|
||||
* The language code for this term.
|
||||
* @param string $expected_label
|
||||
* The label the migrated entity should have.
|
||||
* @param string $expected_vid
|
||||
* The parent vocabulary the migrated entity should have.
|
||||
* @param string $expected_description
|
||||
* The description the migrated entity should have.
|
||||
* @param string $expected_format
|
||||
* The format the migrated entity should have.
|
||||
* @param int $expected_weight
|
||||
* The weight the migrated entity should have.
|
||||
* @param array $expected_parents
|
||||
* The parent terms the migrated entity should have.
|
||||
* @param int $expected_field_integer_value
|
||||
* The value the migrated entity field should have.
|
||||
* @param int $expected_term_reference_tid
|
||||
* The term reference ID the migrated entity field should have.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertEntity(int $id, string $expected_language, string $expected_label, string $expected_vid, string $expected_description = '', ?string $expected_format = NULL, int $expected_weight = 0, array $expected_parents = [], ?int $expected_field_integer_value = NULL, ?int $expected_term_reference_tid = NULL): void {
|
||||
/** @var \Drupal\taxonomy\TermInterface $entity */
|
||||
$entity = Term::load($id);
|
||||
$this->assertInstanceOf(TermInterface::class, $entity);
|
||||
$this->assertSame($expected_language, $entity->language()->getId());
|
||||
$this->assertSame($expected_label, $entity->label());
|
||||
$this->assertSame($expected_vid, $entity->bundle());
|
||||
$this->assertSame($expected_description, $entity->getDescription());
|
||||
$this->assertSame($expected_format, $entity->getFormat());
|
||||
$this->assertSame($expected_weight, $entity->getWeight());
|
||||
$this->assertHierarchy($expected_vid, $id, $expected_parents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that a term is present in the tree storage, with the right parents.
|
||||
*
|
||||
* @param string $vid
|
||||
* Vocabulary ID.
|
||||
* @param int $tid
|
||||
* ID of the term to check.
|
||||
* @param array $parent_ids
|
||||
* The expected parent term IDs.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertHierarchy(string $vid, int $tid, array $parent_ids): void {
|
||||
if (!isset($this->treeData[$vid])) {
|
||||
$tree = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadTree($vid);
|
||||
$this->treeData[$vid] = [];
|
||||
foreach ($tree as $item) {
|
||||
$this->treeData[$vid][$item->tid] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertArrayHasKey($tid, $this->treeData[$vid], "Term $tid exists in taxonomy tree");
|
||||
$term = $this->treeData[$vid][$tid];
|
||||
// PostgreSQL, MySQL and SQLite may not return the parent terms in the same
|
||||
// order so sort before testing.
|
||||
sort($parent_ids);
|
||||
$actual_terms = array_filter($term->parents);
|
||||
sort($actual_terms);
|
||||
$this->assertEquals($parent_ids, $actual_terms, "Term $tid has correct parents in taxonomy tree");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the Drupal 6 i18n taxonomy term to Drupal 8 migration.
|
||||
*/
|
||||
public function testTranslatedTaxonomyTerms(): void {
|
||||
$this->assertEntity(
|
||||
1,
|
||||
'zu',
|
||||
'zu - term 1 of vocabulary 1',
|
||||
'vocabulary_1_i_0_',
|
||||
'zu - description of term 1 of vocabulary 1',
|
||||
NULL,
|
||||
0,
|
||||
[]
|
||||
);
|
||||
$this->assertEntity(
|
||||
2,
|
||||
'fr',
|
||||
'fr - term 2 of vocabulary 2',
|
||||
'vocabulary_2_i_1_',
|
||||
'fr - description of term 2 of vocabulary 2',
|
||||
NULL,
|
||||
3,
|
||||
[]
|
||||
);
|
||||
$this->assertEntity(
|
||||
3,
|
||||
'fr',
|
||||
'fr - term 3 of vocabulary 2',
|
||||
'vocabulary_2_i_1_',
|
||||
'fr - description of term 3 of vocabulary 2',
|
||||
NULL,
|
||||
4,
|
||||
['2']
|
||||
);
|
||||
$this->assertEntity(
|
||||
4,
|
||||
'en',
|
||||
'term 4 of vocabulary 3',
|
||||
'vocabulary_3_i_2_',
|
||||
'description of term 4 of vocabulary 3',
|
||||
NULL,
|
||||
6,
|
||||
[]
|
||||
);
|
||||
$this->assertEntity(
|
||||
5,
|
||||
'en',
|
||||
'term 5 of vocabulary 3',
|
||||
'vocabulary_3_i_2_',
|
||||
'description of term 5 of vocabulary 3',
|
||||
NULL,
|
||||
7,
|
||||
['4']
|
||||
);
|
||||
$this->assertEntity(
|
||||
6,
|
||||
'en',
|
||||
'term 6 of vocabulary 3',
|
||||
'vocabulary_3_i_2_',
|
||||
'description of term 6 of vocabulary 3',
|
||||
NULL,
|
||||
8,
|
||||
['4', '5']
|
||||
);
|
||||
$this->assertEntity(
|
||||
7,
|
||||
'fr',
|
||||
'fr - term 2 of vocabulary 1',
|
||||
'vocabulary_1_i_0_',
|
||||
'fr - desc of term 2 vocab 1',
|
||||
NULL,
|
||||
0,
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Kernel\Migrate\d7;
|
||||
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
|
||||
|
||||
/**
|
||||
* Tests the migration of entity translation settings.
|
||||
*
|
||||
* @group migrate_drupal_7
|
||||
*/
|
||||
class MigrateEntityTranslationSettingsTest extends MigrateDrupal7TestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'comment',
|
||||
'content_translation',
|
||||
'language',
|
||||
'menu_ui',
|
||||
'node',
|
||||
'taxonomy',
|
||||
'text',
|
||||
'user',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->installConfig([
|
||||
'comment',
|
||||
'content_translation',
|
||||
'node',
|
||||
'taxonomy',
|
||||
'user',
|
||||
]);
|
||||
|
||||
$this->installEntitySchema('comment');
|
||||
$this->installEntitySchema('node');
|
||||
$this->installEntitySchema('taxonomy_term');
|
||||
$this->installEntitySchema('user');
|
||||
|
||||
$this->executeMigrations([
|
||||
'language',
|
||||
'd7_comment_type',
|
||||
'd7_node_type',
|
||||
'd7_taxonomy_vocabulary',
|
||||
'd7_entity_translation_settings',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests entity translation settings migration.
|
||||
*/
|
||||
public function testEntityTranslationSettingsMigration(): void {
|
||||
// Tests 'comment_node_test_content_type' entity translation settings.
|
||||
$config = $this->config('language.content_settings.comment.comment_node_test_content_type');
|
||||
$this->assertSame($config->get('target_entity_type_id'), 'comment');
|
||||
$this->assertSame($config->get('target_bundle'), 'comment_node_test_content_type');
|
||||
$this->assertSame($config->get('default_langcode'), 'current_interface');
|
||||
$this->assertFalse((bool) $config->get('language_alterable'));
|
||||
$this->assertTrue((bool) $config->get('third_party_settings.content_translation.enabled'));
|
||||
$this->assertFalse((bool) $config->get('third_party_settings.content_translation.bundle_settings.untranslatable_fields_hide'));
|
||||
|
||||
// Tests 'test_content_type' entity translation settings.
|
||||
$config = $this->config('language.content_settings.node.test_content_type');
|
||||
$this->assertSame($config->get('target_entity_type_id'), 'node');
|
||||
$this->assertSame($config->get('target_bundle'), 'test_content_type');
|
||||
$this->assertSame($config->get('default_langcode'), LanguageInterface::LANGCODE_NOT_SPECIFIED);
|
||||
$this->assertTrue((bool) $config->get('language_alterable'));
|
||||
$this->assertTrue((bool) $config->get('third_party_settings.content_translation.enabled'));
|
||||
$this->assertFalse((bool) $config->get('third_party_settings.content_translation.bundle_settings.untranslatable_fields_hide'));
|
||||
|
||||
// Tests 'test_vocabulary' entity translation settings.
|
||||
$config = $this->config('language.content_settings.taxonomy_term.test_vocabulary');
|
||||
$this->assertSame($config->get('target_entity_type_id'), 'taxonomy_term');
|
||||
$this->assertSame($config->get('target_bundle'), 'test_vocabulary');
|
||||
$this->assertSame($config->get('default_langcode'), LanguageInterface::LANGCODE_SITE_DEFAULT);
|
||||
$this->assertFalse((bool) $config->get('language_alterable'));
|
||||
$this->assertTrue((bool) $config->get('third_party_settings.content_translation.enabled'));
|
||||
$this->assertFalse((bool) $config->get('third_party_settings.content_translation.bundle_settings.untranslatable_fields_hide'));
|
||||
|
||||
// Tests 'user' entity translation settings.
|
||||
$config = $this->config('language.content_settings.user.user');
|
||||
$this->assertSame($config->get('target_entity_type_id'), 'user');
|
||||
$this->assertSame($config->get('target_bundle'), 'user');
|
||||
$this->assertSame($config->get('default_langcode'), LanguageInterface::LANGCODE_SITE_DEFAULT);
|
||||
$this->assertFalse((bool) $config->get('language_alterable'));
|
||||
$this->assertTrue((bool) $config->get('third_party_settings.content_translation.enabled'));
|
||||
$this->assertFalse((bool) $config->get('third_party_settings.content_translation.bundle_settings.untranslatable_fields_hide'));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,256 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Kernel\Plugin\migrate\source\d7;
|
||||
|
||||
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
|
||||
|
||||
/**
|
||||
* Tests entity translation settings source plugin.
|
||||
*
|
||||
* @covers \Drupal\content_translation\Plugin\migrate\source\d7\EntityTranslationSettings
|
||||
*
|
||||
* @group content_translation
|
||||
*/
|
||||
class EntityTranslationSettingsTest extends MigrateSqlSourceTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'content_translation',
|
||||
'language',
|
||||
'migrate_drupal',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function providerSource() {
|
||||
$tests = [];
|
||||
|
||||
// Source data when there's no entity type that uses entity translation.
|
||||
$tests[0]['source_data']['variable'] = [
|
||||
[
|
||||
'name' => 'entity_translation_entity_types',
|
||||
'value' => 'a:4:{s:7:"comment";i:0;s:4:"node";i:0;s:13:"taxonomy_term";i:0;s:4:"user";i:0;}',
|
||||
],
|
||||
];
|
||||
|
||||
// Source data when there's no bundle settings variables.
|
||||
$tests[1]['source_data']['variable'] = [
|
||||
[
|
||||
'name' => 'entity_translation_entity_types',
|
||||
'value' => 'a:4:{s:7:"comment";s:7:"comment";s:4:"node";s:4:"node";s:13:"taxonomy_term";s:13:"taxonomy_term";s:4:"user";s:4:"user";}',
|
||||
],
|
||||
[
|
||||
'name' => 'entity_translation_taxonomy',
|
||||
'value' => 'a:3:{s:6:"forums";b:1;s:4:"tags";b:1;s:4:"test";b:0;}',
|
||||
],
|
||||
[
|
||||
'name' => 'language_content_type_article',
|
||||
'value' => 's:1:"2";',
|
||||
],
|
||||
[
|
||||
'name' => 'language_content_type_forum',
|
||||
'value' => 's:1:"4";',
|
||||
],
|
||||
[
|
||||
'name' => 'language_content_type_page',
|
||||
'value' => 's:1:"4";',
|
||||
],
|
||||
];
|
||||
|
||||
// Source data when there's bundle settings variables.
|
||||
$tests[2]['source_data']['variable'] = [
|
||||
[
|
||||
'name' => 'entity_translation_entity_types',
|
||||
'value' => 'a:4:{s:7:"comment";s:7:"comment";s:4:"node";s:4:"node";s:13:"taxonomy_term";s:13:"taxonomy_term";s:4:"user";s:4:"user";}',
|
||||
],
|
||||
[
|
||||
'name' => 'entity_translation_settings_comment__comment_node_forum',
|
||||
'value' => 'a:5:{s:16:"default_language";s:12:"xx-et-author";s:22:"hide_language_selector";i:1;s:21:"exclude_language_none";i:0;s:13:"lock_language";i:0;s:27:"shared_fields_original_only";i:0;}',
|
||||
],
|
||||
[
|
||||
'name' => 'entity_translation_settings_comment__comment_node_page',
|
||||
'value' => 'a:5:{s:16:"default_language";s:12:"xx-et-author";s:22:"hide_language_selector";i:0;s:21:"exclude_language_none";i:0;s:13:"lock_language";i:0;s:27:"shared_fields_original_only";i:1;}',
|
||||
],
|
||||
[
|
||||
'name' => 'entity_translation_settings_node__forum',
|
||||
'value' => 'a:5:{s:16:"default_language";s:12:"xx-et-author";s:22:"hide_language_selector";i:0;s:21:"exclude_language_none";i:0;s:13:"lock_language";i:0;s:27:"shared_fields_original_only";i:0;}',
|
||||
],
|
||||
[
|
||||
'name' => 'entity_translation_settings_node__page',
|
||||
'value' => 'a:5:{s:16:"default_language";s:13:"xx-et-default";s:22:"hide_language_selector";i:1;s:21:"exclude_language_none";i:0;s:13:"lock_language";i:0;s:27:"shared_fields_original_only";i:1;}',
|
||||
],
|
||||
[
|
||||
'name' => 'entity_translation_settings_taxonomy_term__forums',
|
||||
'value' => 'a:5:{s:16:"default_language";s:13:"xx-et-current";s:22:"hide_language_selector";i:0;s:21:"exclude_language_none";i:0;s:13:"lock_language";i:0;s:27:"shared_fields_original_only";i:1;}',
|
||||
],
|
||||
[
|
||||
'name' => 'entity_translation_settings_taxonomy_term__tags',
|
||||
'value' => 'a:5:{s:16:"default_language";s:13:"xx-et-current";s:22:"hide_language_selector";i:1;s:21:"exclude_language_none";i:0;s:13:"lock_language";i:0;s:27:"shared_fields_original_only";i:0;}',
|
||||
],
|
||||
[
|
||||
'name' => 'entity_translation_settings_user__user',
|
||||
'value' => 'a:5:{s:16:"default_language";s:12:"xx-et-author";s:22:"hide_language_selector";i:1;s:21:"exclude_language_none";i:0;s:13:"lock_language";i:0;s:27:"shared_fields_original_only";i:1;}',
|
||||
],
|
||||
[
|
||||
'name' => 'entity_translation_taxonomy',
|
||||
'value' => 'a:3:{s:6:"forums";b:1;s:4:"tags";b:1;s:4:"test";b:0;}',
|
||||
],
|
||||
[
|
||||
'name' => 'language_content_type_article',
|
||||
'value' => 's:1:"2";',
|
||||
],
|
||||
[
|
||||
'name' => 'language_content_type_forum',
|
||||
'value' => 's:1:"4";',
|
||||
],
|
||||
[
|
||||
'name' => 'language_content_type_page',
|
||||
'value' => 's:1:"4";',
|
||||
],
|
||||
];
|
||||
|
||||
// Source data when taxonomy terms are translatable but the
|
||||
// 'entity_translation_taxonomy' variable is not set.
|
||||
$tests[3]['source_data']['variable'] = [
|
||||
[
|
||||
'name' => 'entity_translation_entity_types',
|
||||
'value' => 'a:4:{s:7:"comment";i:0;s:4:"node";i:0;s:13:"taxonomy_term";i:1;s:4:"user";i:0;}',
|
||||
],
|
||||
];
|
||||
|
||||
// Expected data when there's no entity type that uses entity translation.
|
||||
$tests[0]['expected_data'] = [];
|
||||
|
||||
// Expected data when there's no bundle settings variables.
|
||||
$tests[1]['expected_data'] = [
|
||||
[
|
||||
'id' => 'node.forum',
|
||||
'target_entity_type_id' => 'node',
|
||||
'target_bundle' => 'forum',
|
||||
'default_langcode' => 'und',
|
||||
'language_alterable' => TRUE,
|
||||
'untranslatable_fields_hide' => FALSE,
|
||||
],
|
||||
[
|
||||
'id' => 'node.page',
|
||||
'target_entity_type_id' => 'node',
|
||||
'target_bundle' => 'page',
|
||||
'default_langcode' => 'und',
|
||||
'language_alterable' => TRUE,
|
||||
'untranslatable_fields_hide' => FALSE,
|
||||
],
|
||||
[
|
||||
'id' => 'comment.comment_forum',
|
||||
'target_entity_type_id' => 'comment',
|
||||
'target_bundle' => 'comment_forum',
|
||||
'default_langcode' => 'xx-et-current',
|
||||
'language_alterable' => FALSE,
|
||||
'untranslatable_fields_hide' => FALSE,
|
||||
],
|
||||
[
|
||||
'id' => 'comment.comment_node_page',
|
||||
'target_entity_type_id' => 'comment',
|
||||
'target_bundle' => 'comment_node_page',
|
||||
'default_langcode' => 'xx-et-current',
|
||||
'language_alterable' => FALSE,
|
||||
'untranslatable_fields_hide' => FALSE,
|
||||
],
|
||||
[
|
||||
'id' => 'taxonomy_term.forums',
|
||||
'target_entity_type_id' => 'taxonomy_term',
|
||||
'target_bundle' => 'forums',
|
||||
'default_langcode' => 'xx-et-default',
|
||||
'language_alterable' => FALSE,
|
||||
'untranslatable_fields_hide' => FALSE,
|
||||
],
|
||||
[
|
||||
'id' => 'taxonomy_term.tags',
|
||||
'target_entity_type_id' => 'taxonomy_term',
|
||||
'target_bundle' => 'tags',
|
||||
'default_langcode' => 'xx-et-default',
|
||||
'language_alterable' => FALSE,
|
||||
'untranslatable_fields_hide' => FALSE,
|
||||
],
|
||||
[
|
||||
'id' => 'user.user',
|
||||
'target_entity_type_id' => 'user',
|
||||
'target_bundle' => 'user',
|
||||
'default_langcode' => 'xx-et-default',
|
||||
'language_alterable' => FALSE,
|
||||
'untranslatable_fields_hide' => FALSE,
|
||||
],
|
||||
];
|
||||
|
||||
// Expected data when there's bundle settings variables.
|
||||
$tests[2]['expected_data'] = [
|
||||
[
|
||||
'id' => 'node.forum',
|
||||
'target_entity_type_id' => 'node',
|
||||
'target_bundle' => 'forum',
|
||||
'default_langcode' => 'xx-et-author',
|
||||
'language_alterable' => TRUE,
|
||||
'untranslatable_fields_hide' => FALSE,
|
||||
],
|
||||
[
|
||||
'id' => 'node.page',
|
||||
'target_entity_type_id' => 'node',
|
||||
'target_bundle' => 'page',
|
||||
'default_langcode' => 'xx-et-default',
|
||||
'language_alterable' => FALSE,
|
||||
'untranslatable_fields_hide' => TRUE,
|
||||
],
|
||||
[
|
||||
'id' => 'comment.comment_forum',
|
||||
'target_entity_type_id' => 'comment',
|
||||
'target_bundle' => 'comment_forum',
|
||||
'default_langcode' => 'xx-et-author',
|
||||
'language_alterable' => FALSE,
|
||||
'untranslatable_fields_hide' => FALSE,
|
||||
],
|
||||
[
|
||||
'id' => 'comment.comment_node_page',
|
||||
'target_entity_type_id' => 'comment',
|
||||
'target_bundle' => 'comment_node_page',
|
||||
'default_langcode' => 'xx-et-author',
|
||||
'language_alterable' => TRUE,
|
||||
'untranslatable_fields_hide' => TRUE,
|
||||
],
|
||||
[
|
||||
'id' => 'taxonomy_term.forums',
|
||||
'target_entity_type_id' => 'taxonomy_term',
|
||||
'target_bundle' => 'forums',
|
||||
'default_langcode' => 'xx-et-current',
|
||||
'language_alterable' => TRUE,
|
||||
'untranslatable_fields_hide' => TRUE,
|
||||
],
|
||||
[
|
||||
'id' => 'taxonomy_term.tags',
|
||||
'target_entity_type_id' => 'taxonomy_term',
|
||||
'target_bundle' => 'tags',
|
||||
'default_langcode' => 'xx-et-current',
|
||||
'language_alterable' => FALSE,
|
||||
'untranslatable_fields_hide' => FALSE,
|
||||
],
|
||||
[
|
||||
'id' => 'user.user',
|
||||
'target_entity_type_id' => 'user',
|
||||
'target_bundle' => 'user',
|
||||
'default_langcode' => 'xx-et-author',
|
||||
'language_alterable' => FALSE,
|
||||
'untranslatable_fields_hide' => TRUE,
|
||||
],
|
||||
];
|
||||
|
||||
// Expected data when taxonomy terms are translatable but the
|
||||
// 'entity_translation_taxonomy' variable is not set.
|
||||
$tests[3]['expected_data'] = [];
|
||||
|
||||
return $tests;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Traits;
|
||||
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Tests\language\Traits\LanguageTestTrait;
|
||||
|
||||
/**
|
||||
* Provides an API to programmatically manage content translation in tests.
|
||||
*/
|
||||
trait ContentTranslationTestTrait {
|
||||
|
||||
use LanguageTestTrait;
|
||||
|
||||
/**
|
||||
* Enables content translation for the given entity type bundle.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The ID of the entity type.
|
||||
* @param string $bundle
|
||||
* The bundle name.
|
||||
* @param string|null $default_langcode
|
||||
* The language code to use as the default language.
|
||||
*/
|
||||
public function enableContentTranslation(string $entity_type_id, string $bundle, ?string $default_langcode = LanguageInterface::LANGCODE_SITE_DEFAULT): void {
|
||||
static::enableBundleTranslation($entity_type_id, $bundle, $default_langcode);
|
||||
$content_translation_manager = $this->container->get('content_translation.manager');
|
||||
$content_translation_manager->setEnabled($entity_type_id, $bundle, TRUE);
|
||||
$content_translation_manager->setBundleTranslationSettings($entity_type_id, $bundle, [
|
||||
'untranslatable_fields_hide' => FALSE,
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Unit\Access;
|
||||
|
||||
use Drupal\content_translation\Access\ContentTranslationManageAccessCheck;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Language\Language;
|
||||
use Drupal\Tests\Core\Entity\ContentEntityBaseMockableClass;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Tests for content translation manage check.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\content_translation\Access\ContentTranslationManageAccessCheck
|
||||
* @group Access
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationManageAccessCheckTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* The cache contexts manager.
|
||||
*
|
||||
* @var \Drupal\Core\Cache\Context\CacheContextsManager|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
protected $cacheContextsManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->cacheContextsManager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$this->cacheContextsManager->method('assertValidTokens')->willReturn(TRUE);
|
||||
|
||||
$container = new ContainerBuilder();
|
||||
$container->set('cache_contexts_manager', $this->cacheContextsManager);
|
||||
\Drupal::setContainer($container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the create access method.
|
||||
*
|
||||
* @covers ::access
|
||||
*/
|
||||
public function testCreateAccess(): void {
|
||||
// Set the mock translation handler.
|
||||
$translation_handler = $this->createMock('\Drupal\content_translation\ContentTranslationHandlerInterface');
|
||||
$translation_handler->expects($this->once())
|
||||
->method('getTranslationAccess')
|
||||
->willReturn(AccessResult::allowed());
|
||||
|
||||
$entity_type_manager = $this->createMock(EntityTypeManagerInterface::class);
|
||||
$entity_type_manager->expects($this->once())
|
||||
->method('getHandler')
|
||||
->withAnyParameters()
|
||||
->willReturn($translation_handler);
|
||||
|
||||
// Set our source and target languages.
|
||||
$source = 'en';
|
||||
$target = 'it';
|
||||
|
||||
// Set the mock language manager.
|
||||
$language_manager = $this->createMock('Drupal\Core\Language\LanguageManagerInterface');
|
||||
$language_manager->expects($this->once())
|
||||
->method('getLanguages')
|
||||
->willReturn([$source => [], $target => []]);
|
||||
$language_manager->expects($this->any())
|
||||
->method('getLanguage')
|
||||
->willReturnMap([
|
||||
[$source, new Language(['id' => $source])],
|
||||
[$target, new Language(['id' => $target])],
|
||||
]);
|
||||
|
||||
// Set the mock entity. We need to use ContentEntityBase for mocking due to
|
||||
// issues with phpunit and multiple interfaces.
|
||||
$entity = $this->getMockBuilder(ContentEntityBaseMockableClass::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$entity->expects($this->once())
|
||||
->method('getEntityTypeId');
|
||||
$entity->expects($this->once())
|
||||
->method('getTranslationLanguages')
|
||||
->with()
|
||||
->willReturn([]);
|
||||
$entity->expects($this->once())
|
||||
->method('getCacheContexts')
|
||||
->willReturn([]);
|
||||
$entity->expects($this->once())
|
||||
->method('getCacheMaxAge')
|
||||
->willReturn(Cache::PERMANENT);
|
||||
$entity->expects($this->once())
|
||||
->method('getCacheTags')
|
||||
->willReturn(['node:1337']);
|
||||
$entity->expects($this->once())
|
||||
->method('getCacheContexts')
|
||||
->willReturn([]);
|
||||
|
||||
// Set the route requirements.
|
||||
$route = new Route('test_route');
|
||||
$route->setRequirement('_access_content_translation_manage', 'create');
|
||||
|
||||
// Set up the route match.
|
||||
$route_match = $this->createMock('Drupal\Core\Routing\RouteMatchInterface');
|
||||
$route_match->expects($this->once())
|
||||
->method('getParameter')
|
||||
->with('node')
|
||||
->willReturn($entity);
|
||||
|
||||
// Set the mock account.
|
||||
$account = $this->createMock('Drupal\Core\Session\AccountInterface');
|
||||
|
||||
// The access check under test.
|
||||
$check = new ContentTranslationManageAccessCheck($entity_type_manager, $language_manager);
|
||||
|
||||
// The request params.
|
||||
$language = 'en';
|
||||
$entity_type_id = 'node';
|
||||
|
||||
$this->assertTrue($check->access($route, $route_match, $account, $source, $target, $language, $entity_type_id)->isAllowed(), "The access check matches");
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\content_translation\Unit\Menu;
|
||||
|
||||
use Drupal\Tests\Core\Menu\LocalTaskIntegrationTestBase;
|
||||
|
||||
/**
|
||||
* Tests content translation local tasks.
|
||||
*
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationLocalTasksTest extends LocalTaskIntegrationTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
$this->directoryList = [
|
||||
'content_translation' => 'core/modules/content_translation',
|
||||
'node' => 'core/modules/node',
|
||||
];
|
||||
parent::setUp();
|
||||
|
||||
$entity_type = $this->createMock('Drupal\Core\Entity\EntityTypeInterface');
|
||||
$entity_type->expects($this->any())
|
||||
->method('getLinkTemplate')
|
||||
->willReturnMap([
|
||||
['canonical', 'entity.node.canonical'],
|
||||
[
|
||||
'drupal:content-translation-overview',
|
||||
'entity.node.content_translation_overview',
|
||||
],
|
||||
]);
|
||||
$content_translation_manager = $this->createMock('Drupal\content_translation\ContentTranslationManagerInterface');
|
||||
$content_translation_manager->expects($this->any())
|
||||
->method('getSupportedEntityTypes')
|
||||
->willReturn([
|
||||
'node' => $entity_type,
|
||||
]);
|
||||
\Drupal::getContainer()->set('content_translation.manager', $content_translation_manager);
|
||||
\Drupal::getContainer()->set('string_translation', $this->getStringTranslationStub());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the block admin display local tasks.
|
||||
*
|
||||
* @dataProvider providerTestBlockAdminDisplay
|
||||
*/
|
||||
public function testBlockAdminDisplay($route, $expected): void {
|
||||
$this->assertLocalTasks($route, $expected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a list of routes to test.
|
||||
*/
|
||||
public static function providerTestBlockAdminDisplay() {
|
||||
return [
|
||||
[
|
||||
'entity.node.canonical',
|
||||
[
|
||||
[
|
||||
'content_translation.local_tasks:entity.node.content_translation_overview',
|
||||
'entity.node.canonical',
|
||||
'entity.node.edit_form',
|
||||
'entity.node.delete_form',
|
||||
'entity.node.version_history',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'entity.node.content_translation_overview',
|
||||
[
|
||||
[
|
||||
'content_translation.local_tasks:entity.node.content_translation_overview',
|
||||
'entity.node.canonical',
|
||||
'entity.node.edit_form',
|
||||
'entity.node.delete_form',
|
||||
'entity.node.version_history',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user