Update Composer, update everything
This commit is contained in:
parent
ea3e94409f
commit
dda5c284b6
19527 changed files with 1135420 additions and 351004 deletions
|
@ -18,3 +18,9 @@ language.content_settings.*.*.third_party.content_translation:
|
|||
enabled:
|
||||
type: boolean
|
||||
label: 'Content translation enabled'
|
||||
bundle_settings:
|
||||
type: sequence
|
||||
label: 'Content translation bundle settings'
|
||||
sequence:
|
||||
type: string
|
||||
label: 'Bundle settings values'
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
/**
|
||||
* @file
|
||||
* Content Translation admin behaviors.
|
||||
*/
|
||||
|
||||
(function($, Drupal, drupalSettings) {
|
||||
/**
|
||||
* Forces applicable options to be checked as translatable.
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*
|
||||
* @prop {Drupal~behaviorAttach} attach
|
||||
* Attaches content translation dependent options to the UI.
|
||||
*/
|
||||
Drupal.behaviors.contentTranslationDependentOptions = {
|
||||
attach(context) {
|
||||
const $context = $(context);
|
||||
const options = drupalSettings.contentTranslationDependentOptions;
|
||||
let $fields;
|
||||
|
||||
function fieldsChangeHandler($fields, dependentColumns) {
|
||||
return function(e) {
|
||||
Drupal.behaviors.contentTranslationDependentOptions.check(
|
||||
$fields,
|
||||
dependentColumns,
|
||||
$(e.target),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
// We're given a generic name to look for so we find all inputs containing
|
||||
// that name and copy over the input values that require all columns to be
|
||||
// translatable.
|
||||
if (options && options.dependent_selectors) {
|
||||
Object.keys(options.dependent_selectors).forEach(field => {
|
||||
$fields = $context.find(`input[name^="${field}"]`);
|
||||
const dependentColumns = options.dependent_selectors[field];
|
||||
|
||||
$fields.on('change', fieldsChangeHandler($fields, dependentColumns));
|
||||
Drupal.behaviors.contentTranslationDependentOptions.check(
|
||||
$fields,
|
||||
dependentColumns,
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
check($fields, dependentColumns, $changed) {
|
||||
let $element = $changed;
|
||||
let column;
|
||||
|
||||
function filterFieldsList(index, field) {
|
||||
return $(field).val() === column;
|
||||
}
|
||||
|
||||
// A field that has many different translatable parts can also define one
|
||||
// or more columns that require all columns to be translatable.
|
||||
Object.keys(dependentColumns || {}).forEach(index => {
|
||||
column = dependentColumns[index];
|
||||
|
||||
if (!$changed) {
|
||||
$element = $fields.filter(filterFieldsList);
|
||||
}
|
||||
|
||||
if ($element.is(`input[value="${column}"]:checked`)) {
|
||||
$fields
|
||||
.prop('checked', true)
|
||||
.not($element)
|
||||
.prop('disabled', true);
|
||||
} else {
|
||||
$fields.prop('disabled', false);
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Makes field translatability inherit bundle translatability.
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*
|
||||
* @prop {Drupal~behaviorAttach} attach
|
||||
* Attaches content translation behavior.
|
||||
*/
|
||||
Drupal.behaviors.contentTranslation = {
|
||||
attach(context) {
|
||||
// Initially hide all field rows for non translatable bundles and all
|
||||
// column rows for non translatable fields.
|
||||
$(context)
|
||||
.find('table .bundle-settings .translatable :input')
|
||||
.once('translation-entity-admin-hide')
|
||||
.each(function() {
|
||||
const $input = $(this);
|
||||
const $bundleSettings = $input.closest('.bundle-settings');
|
||||
if (!$input.is(':checked')) {
|
||||
$bundleSettings.nextUntil('.bundle-settings').hide();
|
||||
} else {
|
||||
$bundleSettings
|
||||
.nextUntil('.bundle-settings', '.field-settings')
|
||||
.find('.translatable :input:not(:checked)')
|
||||
.closest('.field-settings')
|
||||
.nextUntil(':not(.column-settings)')
|
||||
.hide();
|
||||
}
|
||||
});
|
||||
|
||||
// When a bundle is made translatable all of its fields should inherit
|
||||
// this setting. Instead when it is made non translatable its fields are
|
||||
// hidden, since their translatability no longer matters.
|
||||
$('body')
|
||||
.once('translation-entity-admin-bind')
|
||||
.on('click', 'table .bundle-settings .translatable :input', e => {
|
||||
const $target = $(e.target);
|
||||
const $bundleSettings = $target.closest('.bundle-settings');
|
||||
const $settings = $bundleSettings.nextUntil('.bundle-settings');
|
||||
const $fieldSettings = $settings.filter('.field-settings');
|
||||
if ($target.is(':checked')) {
|
||||
$bundleSettings
|
||||
.find('.operations :input[name$="[language_alterable]"]')
|
||||
.prop('checked', true);
|
||||
$fieldSettings.find('.translatable :input').prop('checked', true);
|
||||
$settings.show();
|
||||
} else {
|
||||
$settings.hide();
|
||||
}
|
||||
})
|
||||
.on('click', 'table .field-settings .translatable :input', e => {
|
||||
const $target = $(e.target);
|
||||
const $fieldSettings = $target.closest('.field-settings');
|
||||
const $columnSettings = $fieldSettings.nextUntil(
|
||||
'.field-settings, .bundle-settings',
|
||||
);
|
||||
if ($target.is(':checked')) {
|
||||
$columnSettings.show();
|
||||
} else {
|
||||
$columnSettings.hide();
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
})(jQuery, Drupal, drupalSettings);
|
|
@ -5,6 +5,8 @@
|
|||
* The content translation administration forms.
|
||||
*/
|
||||
|
||||
use Drupal\content_translation\BundleTranslationSettingsInterface;
|
||||
use Drupal\content_translation\ContentTranslationManager;
|
||||
use Drupal\Core\Config\Entity\ThirdPartySettingsInterface;
|
||||
use Drupal\Core\Entity\ContentEntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
|
@ -63,7 +65,7 @@ function content_translation_field_sync_widget(FieldDefinitionInterface $field,
|
|||
// does not get lost.
|
||||
$element[key($options)]['#attached']['drupalSettings']['contentTranslationDependentOptions'] = [
|
||||
'dependent_selectors' => [
|
||||
$element_name => $require_all_groups_for_translation
|
||||
$element_name => $require_all_groups_for_translation,
|
||||
],
|
||||
];
|
||||
$element[key($options)]['#attached']['library'][] = 'content_translation/drupal.content_translation.admin';
|
||||
|
@ -83,6 +85,7 @@ function _content_translation_form_language_content_settings_form_alter(array &$
|
|||
return;
|
||||
}
|
||||
|
||||
/** @var \Drupal\content_translation\ContentTranslationManagerInterface $content_translation_manager */
|
||||
$content_translation_manager = \Drupal::service('content_translation.manager');
|
||||
$default = $form['entity_types']['#default_value'];
|
||||
foreach ($default as $entity_type_id => $enabled) {
|
||||
|
@ -110,6 +113,26 @@ function _content_translation_form_language_content_settings_form_alter(array &$
|
|||
continue;
|
||||
}
|
||||
|
||||
// Displayed the "shared fields widgets" toggle.
|
||||
if ($content_translation_manager instanceof BundleTranslationSettingsInterface) {
|
||||
$settings = $content_translation_manager->getBundleTranslationSettings($entity_type_id, $bundle);
|
||||
$force_hidden = ContentTranslationManager::isPendingRevisionSupportEnabled($entity_type_id, $bundle);
|
||||
$form['settings'][$entity_type_id][$bundle]['settings']['content_translation']['untranslatable_fields_hide'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Hide non translatable fields on translation forms'),
|
||||
'#default_value' => $force_hidden || !empty($settings['untranslatable_fields_hide']),
|
||||
'#disabled' => $force_hidden,
|
||||
'#description' => $force_hidden ? t('Moderated content requires non-translatable fields to be edited in the original language form.') : '',
|
||||
'#states' => [
|
||||
'visible' => [
|
||||
':input[name="settings[' . $entity_type_id . '][' . $bundle . '][translatable]"]' => [
|
||||
'checked' => TRUE,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$fields = $entity_manager->getFieldDefinitions($entity_type_id, $bundle);
|
||||
if ($fields) {
|
||||
foreach ($fields as $field_name => $definition) {
|
||||
|
@ -141,6 +164,7 @@ function _content_translation_form_language_content_settings_form_alter(array &$
|
|||
$form['#validate'][] = 'content_translation_form_language_content_settings_validate';
|
||||
$form['#submit'][] = 'content_translation_form_language_content_settings_submit';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether translatability should be configurable for a field.
|
||||
*
|
||||
|
@ -209,7 +233,7 @@ function _content_translation_preprocess_language_content_settings_table(&$varia
|
|||
$rows[] = [
|
||||
'data' => [
|
||||
[
|
||||
'data' => drupal_render($field_element),
|
||||
'data' => \Drupal::service('renderer')->render($field_element),
|
||||
'class' => ['translatable'],
|
||||
],
|
||||
[
|
||||
|
@ -243,7 +267,7 @@ function _content_translation_preprocess_language_content_settings_table(&$varia
|
|||
$rows[] = [
|
||||
'data' => [
|
||||
[
|
||||
'data' => drupal_render($column_element[$key]),
|
||||
'data' => \Drupal::service('renderer')->render($column_element[$key]),
|
||||
'class' => ['translatable'],
|
||||
],
|
||||
[
|
||||
|
@ -317,6 +341,8 @@ function content_translation_form_language_content_settings_validate(array $form
|
|||
* @see content_translation_admin_settings_form_validate()
|
||||
*/
|
||||
function content_translation_form_language_content_settings_submit(array $form, FormStateInterface $form_state) {
|
||||
/** @var \Drupal\content_translation\ContentTranslationManagerInterface $content_translation_manager */
|
||||
$content_translation_manager = \Drupal::service('content_translation.manager');
|
||||
$entity_types = $form_state->getValue('entity_types');
|
||||
$settings = &$form_state->getValue('settings');
|
||||
|
||||
|
@ -347,7 +373,12 @@ function content_translation_form_language_content_settings_submit(array $form,
|
|||
}
|
||||
if (isset($bundle_settings['translatable'])) {
|
||||
// Store whether a bundle has translation enabled or not.
|
||||
\Drupal::service('content_translation.manager')->setEnabled($entity_type_id, $bundle, $bundle_settings['translatable']);
|
||||
$content_translation_manager->setEnabled($entity_type_id, $bundle, $bundle_settings['translatable']);
|
||||
|
||||
// Store any other bundle settings.
|
||||
if ($content_translation_manager instanceof BundleTranslationSettingsInterface) {
|
||||
$content_translation_manager->setBundleTranslationSettings($entity_type_id, $bundle, $bundle_settings['settings']['content_translation']);
|
||||
}
|
||||
|
||||
// Save translation_sync settings.
|
||||
if (!empty($bundle_settings['columns'])) {
|
||||
|
@ -367,8 +398,8 @@ function content_translation_form_language_content_settings_submit(array $form,
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure entity and menu router information are correctly rebuilt.
|
||||
\Drupal::entityManager()->clearCachedDefinitions();
|
||||
\Drupal::service('router.builder')->setRebuildNeeded();
|
||||
|
||||
}
|
||||
|
|
|
@ -1,105 +1,69 @@
|
|||
/**
|
||||
* @file
|
||||
* Content Translation admin behaviors.
|
||||
*/
|
||||
* DO NOT EDIT THIS FILE.
|
||||
* See the following change record for more information,
|
||||
* https://www.drupal.org/node/2815083
|
||||
* @preserve
|
||||
**/
|
||||
|
||||
(function ($, Drupal, drupalSettings) {
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Forces applicable options to be checked as translatable.
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*
|
||||
* @prop {Drupal~behaviorAttach} attach
|
||||
* Attaches content translation dependent options to the UI.
|
||||
*/
|
||||
Drupal.behaviors.contentTranslationDependentOptions = {
|
||||
attach: function (context) {
|
||||
attach: function attach(context) {
|
||||
var $context = $(context);
|
||||
var options = drupalSettings.contentTranslationDependentOptions;
|
||||
var $fields;
|
||||
var dependent_columns;
|
||||
var $fields = void 0;
|
||||
|
||||
function fieldsChangeHandler($fields, dependent_columns) {
|
||||
function fieldsChangeHandler($fields, dependentColumns) {
|
||||
return function (e) {
|
||||
Drupal.behaviors.contentTranslationDependentOptions.check($fields, dependent_columns, $(e.target));
|
||||
Drupal.behaviors.contentTranslationDependentOptions.check($fields, dependentColumns, $(e.target));
|
||||
};
|
||||
}
|
||||
|
||||
// We're given a generic name to look for so we find all inputs containing
|
||||
// that name and copy over the input values that require all columns to be
|
||||
// translatable.
|
||||
if (options && options.dependent_selectors) {
|
||||
for (var field in options.dependent_selectors) {
|
||||
if (options.dependent_selectors.hasOwnProperty(field)) {
|
||||
$fields = $context.find('input[name^="' + field + '"]');
|
||||
dependent_columns = options.dependent_selectors[field];
|
||||
Object.keys(options.dependent_selectors).forEach(function (field) {
|
||||
$fields = $context.find('input[name^="' + field + '"]');
|
||||
var dependentColumns = options.dependent_selectors[field];
|
||||
|
||||
$fields.on('change', fieldsChangeHandler($fields, dependent_columns));
|
||||
Drupal.behaviors.contentTranslationDependentOptions.check($fields, dependent_columns);
|
||||
}
|
||||
}
|
||||
$fields.on('change', fieldsChangeHandler($fields, dependentColumns));
|
||||
Drupal.behaviors.contentTranslationDependentOptions.check($fields, dependentColumns);
|
||||
});
|
||||
}
|
||||
},
|
||||
check: function ($fields, dependent_columns, $changed) {
|
||||
check: function check($fields, dependentColumns, $changed) {
|
||||
var $element = $changed;
|
||||
var column;
|
||||
var column = void 0;
|
||||
|
||||
function filterFieldsList(index, field) {
|
||||
return $(field).val() === column;
|
||||
}
|
||||
|
||||
// A field that has many different translatable parts can also define one
|
||||
// or more columns that require all columns to be translatable.
|
||||
for (var index in dependent_columns) {
|
||||
if (dependent_columns.hasOwnProperty(index)) {
|
||||
column = dependent_columns[index];
|
||||
|
||||
if (!$changed) {
|
||||
$element = $fields.filter(filterFieldsList);
|
||||
}
|
||||
|
||||
if ($element.is('input[value="' + column + '"]:checked')) {
|
||||
$fields.prop('checked', true)
|
||||
.not($element).prop('disabled', true);
|
||||
}
|
||||
else {
|
||||
$fields.prop('disabled', false);
|
||||
}
|
||||
Object.keys(dependentColumns || {}).forEach(function (index) {
|
||||
column = dependentColumns[index];
|
||||
|
||||
if (!$changed) {
|
||||
$element = $fields.filter(filterFieldsList);
|
||||
}
|
||||
}
|
||||
|
||||
if ($element.is('input[value="' + column + '"]:checked')) {
|
||||
$fields.prop('checked', true).not($element).prop('disabled', true);
|
||||
} else {
|
||||
$fields.prop('disabled', false);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Makes field translatability inherit bundle translatability.
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*
|
||||
* @prop {Drupal~behaviorAttach} attach
|
||||
* Attaches content translation behavior.
|
||||
*/
|
||||
Drupal.behaviors.contentTranslation = {
|
||||
attach: function (context) {
|
||||
// Initially hide all field rows for non translatable bundles and all
|
||||
// column rows for non translatable fields.
|
||||
attach: function attach(context) {
|
||||
$(context).find('table .bundle-settings .translatable :input').once('translation-entity-admin-hide').each(function () {
|
||||
var $input = $(this);
|
||||
var $bundleSettings = $input.closest('.bundle-settings');
|
||||
if (!$input.is(':checked')) {
|
||||
$bundleSettings.nextUntil('.bundle-settings').hide();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
$bundleSettings.nextUntil('.bundle-settings', '.field-settings').find('.translatable :input:not(:checked)').closest('.field-settings').nextUntil(':not(.column-settings)').hide();
|
||||
}
|
||||
});
|
||||
|
||||
// When a bundle is made translatable all of its fields should inherit
|
||||
// this setting. Instead when it is made non translatable its fields are
|
||||
// hidden, since their translatability no longer matters.
|
||||
$('body').once('translation-entity-admin-bind').on('click', 'table .bundle-settings .translatable :input', function (e) {
|
||||
var $target = $(e.target);
|
||||
var $bundleSettings = $target.closest('.bundle-settings');
|
||||
|
@ -109,23 +73,19 @@
|
|||
$bundleSettings.find('.operations :input[name$="[language_alterable]"]').prop('checked', true);
|
||||
$fieldSettings.find('.translatable :input').prop('checked', true);
|
||||
$settings.show();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
$settings.hide();
|
||||
}
|
||||
})
|
||||
.on('click', 'table .field-settings .translatable :input', function (e) {
|
||||
var $target = $(e.target);
|
||||
var $fieldSettings = $target.closest('.field-settings');
|
||||
var $columnSettings = $fieldSettings.nextUntil('.field-settings, .bundle-settings');
|
||||
if ($target.is(':checked')) {
|
||||
$columnSettings.show();
|
||||
}
|
||||
else {
|
||||
$columnSettings.hide();
|
||||
}
|
||||
});
|
||||
}).on('click', 'table .field-settings .translatable :input', function (e) {
|
||||
var $target = $(e.target);
|
||||
var $fieldSettings = $target.closest('.field-settings');
|
||||
var $columnSettings = $fieldSettings.nextUntil('.field-settings, .bundle-settings');
|
||||
if ($target.is(':checked')) {
|
||||
$columnSettings.show();
|
||||
} else {
|
||||
$columnSettings.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
})(jQuery, Drupal, drupalSettings);
|
||||
})(jQuery, Drupal, drupalSettings);
|
|
@ -2,7 +2,7 @@ name: 'Content Translation'
|
|||
type: module
|
||||
description: 'Allows users to translate content entities.'
|
||||
dependencies:
|
||||
- language
|
||||
- drupal:language
|
||||
package: Multilingual
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
* Installation functions for Content Translation module.
|
||||
*/
|
||||
|
||||
use \Drupal\Core\Url;
|
||||
use Drupal\Core\Entity\Sql\SqlEntityStorageInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Implements hook_install().
|
||||
|
@ -18,17 +20,17 @@ function content_translation_install() {
|
|||
// Translation works when at least two languages are added.
|
||||
if (count(\Drupal::languageManager()->getLanguages()) < 2) {
|
||||
$t_args = [
|
||||
':language_url' => Url::fromRoute('entity.configurable_language.collection')->toString()
|
||||
':language_url' => Url::fromRoute('entity.configurable_language.collection')->toString(),
|
||||
];
|
||||
$message = t('This site has only a single language enabled. <a href=":language_url">Add at least one more language</a> in order to translate content.', $t_args);
|
||||
drupal_set_message($message, 'warning');
|
||||
\Drupal::messenger()->addWarning($message);
|
||||
}
|
||||
// Point the user to the content translation settings.
|
||||
$t_args = [
|
||||
':settings_url' => Url::fromRoute('language.content_settings_page')->toString()
|
||||
':settings_url' => Url::fromRoute('language.content_settings_page')->toString(),
|
||||
];
|
||||
$message = t('<a href=":settings_url">Enable translation</a> for <em>content types</em>, <em>taxonomy vocabularies</em>, <em>accounts</em>, or any other element you wish to translate.', $t_args);
|
||||
drupal_set_message($message, 'warning');
|
||||
\Drupal::messenger()->addWarning($message);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -44,3 +46,58 @@ function content_translation_update_8001() {
|
|||
function content_translation_update_8002() {
|
||||
\Drupal::service('plugin.manager.field.field_type')->clearCachedDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix the initial values for content translation metadata fields.
|
||||
*/
|
||||
function content_translation_update_8400() {
|
||||
$database = \Drupal::database();
|
||||
/** @var \Drupal\content_translation\ContentTranslationManagerInterface $content_translation_manager */
|
||||
$content_translation_manager = \Drupal::service('content_translation.manager');
|
||||
/** @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $last_installed_schema_repository */
|
||||
$last_installed_schema_repository = \Drupal::service('entity.last_installed_schema.repository');
|
||||
$entity_type_manager = \Drupal::entityTypeManager();
|
||||
$entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager();
|
||||
|
||||
$entity_type_manager->clearCachedDefinitions();
|
||||
foreach ($content_translation_manager->getSupportedEntityTypes() as $entity_type_id => $entity_type_definition) {
|
||||
$storage = $entity_type_manager->getStorage($entity_type_id);
|
||||
if ($storage instanceof SqlEntityStorageInterface) {
|
||||
$entity_type = $entity_definition_update_manager->getEntityType($entity_type_id);
|
||||
$storage_definitions = $last_installed_schema_repository->getLastInstalledFieldStorageDefinitions($entity_type_id);
|
||||
|
||||
// Since the entity type is managed by Content Translation, we can assume
|
||||
// that it is translatable, so we use the data and revision data tables.
|
||||
$tables_to_update = [$entity_type->getDataTable()];
|
||||
if ($entity_type->isRevisionable()) {
|
||||
$tables_to_update += [$entity_type->getRevisionDataTable()];
|
||||
}
|
||||
|
||||
foreach ($tables_to_update as $table_name) {
|
||||
// Fix the values of the 'content_translation_source' field.
|
||||
if (isset($storage_definitions['content_translation_source'])) {
|
||||
$database->update($table_name)
|
||||
->fields(['content_translation_source' => LanguageInterface::LANGCODE_NOT_SPECIFIED])
|
||||
->isNull('content_translation_source')
|
||||
->execute();
|
||||
}
|
||||
|
||||
// Fix the values of the 'content_translation_outdated' field.
|
||||
if (isset($storage_definitions['content_translation_outdated'])) {
|
||||
$database->update($table_name)
|
||||
->fields(['content_translation_outdated' => 0])
|
||||
->isNull('content_translation_outdated')
|
||||
->execute();
|
||||
}
|
||||
|
||||
// Fix the values of the 'content_translation_status' field.
|
||||
if (isset($storage_definitions['content_translation_status'])) {
|
||||
$database->update($table_name)
|
||||
->fields(['content_translation_status' => 1])
|
||||
->isNull('content_translation_status')
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* Allows entities to be translated into different languages.
|
||||
*/
|
||||
|
||||
use Drupal\content_translation\BundleTranslationSettingsInterface;
|
||||
use Drupal\content_translation\ContentTranslationManager;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Entity\ContentEntityFormInterface;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
|
@ -59,6 +61,14 @@ function content_translation_module_implements_alter(&$implementations, $hook) {
|
|||
unset($implementations['content_translation']);
|
||||
$implementations['content_translation'] = $group;
|
||||
break;
|
||||
|
||||
// Move our hook_entity_bundle_info_alter() implementation to the top of the
|
||||
// list, so that any other hook implementation can rely on bundles being
|
||||
// correctly marked as translatable.
|
||||
case 'entity_bundle_info_alter':
|
||||
$group = $implementations['content_translation'];
|
||||
$implementations = ['content_translation' => $group] + $implementations;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -154,6 +164,8 @@ function content_translation_entity_type_alter(array &$entity_types) {
|
|||
}
|
||||
$entity_type->set('translation', $translation);
|
||||
}
|
||||
|
||||
$entity_type->addConstraint('ContentTranslationSynchronizedFields');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -161,9 +173,19 @@ function content_translation_entity_type_alter(array &$entity_types) {
|
|||
* Implements hook_entity_bundle_info_alter().
|
||||
*/
|
||||
function content_translation_entity_bundle_info_alter(&$bundles) {
|
||||
foreach ($bundles as $entity_type => &$info) {
|
||||
/** @var \Drupal\content_translation\ContentTranslationManagerInterface $content_translation_manager */
|
||||
$content_translation_manager = \Drupal::service('content_translation.manager');
|
||||
foreach ($bundles as $entity_type_id => &$info) {
|
||||
foreach ($info as $bundle => &$bundle_info) {
|
||||
$bundle_info['translatable'] = \Drupal::service('content_translation.manager')->isEnabled($entity_type, $bundle);
|
||||
$bundle_info['translatable'] = $content_translation_manager->isEnabled($entity_type_id, $bundle);
|
||||
if ($bundle_info['translatable'] && $content_translation_manager instanceof BundleTranslationSettingsInterface) {
|
||||
$settings = $content_translation_manager->getBundleTranslationSettings($entity_type_id, $bundle);
|
||||
// If pending revision support is enabled for this bundle, we need to
|
||||
// hide untranslatable field widgets, otherwise changes in pending
|
||||
// revisions might be overridden by changes in later default revisions.
|
||||
$bundle_info['untranslatable_fields.default_translation_affected'] =
|
||||
!empty($settings['untranslatable_fields_hide']) || ContentTranslationManager::isPendingRevisionSupportEnabled($entity_type_id, $bundle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -183,8 +205,8 @@ function content_translation_entity_base_field_info(EntityTypeInterface $entity_
|
|||
// when translation is disabled.
|
||||
// @todo Re-evaluate this approach and consider removing field storage
|
||||
// definitions and the related field data if the entity type has no bundle
|
||||
// enabled for translation, once base field purging is supported.
|
||||
// See https://www.drupal.org/node/2282119.
|
||||
// enabled for translation.
|
||||
// @see https://www.drupal.org/node/2907777
|
||||
if ($manager->isEnabled($entity_type_id) || array_intersect_key($definitions, $installed_storage_definitions)) {
|
||||
return $definitions;
|
||||
}
|
||||
|
@ -311,14 +333,21 @@ function content_translation_form_alter(array &$form, FormStateInterface $form_s
|
|||
// Handle fields shared between translations when there is at least one
|
||||
// translation available or a new one is being created.
|
||||
if (!$entity->isNew() && (!isset($translations[$form_langcode]) || count($translations) > 1)) {
|
||||
$langcode_key = $entity->getEntityType()->getKey('langcode');
|
||||
foreach ($entity->getFieldDefinitions() as $field_name => $definition) {
|
||||
if (isset($form[$field_name]) && $field_name != $langcode_key) {
|
||||
|
||||
// Allow the widget to define if it should be treated as multilingual
|
||||
// by respecting an already set #multilingual key.
|
||||
if (isset($form[$field_name]) && !isset($form[$field_name]['#multilingual'])) {
|
||||
$form[$field_name]['#multilingual'] = $definition->isTranslatable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The footer region, if defined, may contain multilingual widgets so we
|
||||
// need to always display it.
|
||||
if (isset($form['footer'])) {
|
||||
$form['footer']['#multilingual'] = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -410,6 +439,11 @@ function content_translation_form_field_config_edit_form_alter(array &$form, For
|
|||
*/
|
||||
function content_translation_entity_presave(EntityInterface $entity) {
|
||||
if ($entity instanceof ContentEntityInterface && $entity->isTranslatable() && !$entity->isNew()) {
|
||||
/** @var \Drupal\content_translation\ContentTranslationManagerInterface $manager */
|
||||
$manager = \Drupal::service('content_translation.manager');
|
||||
if (!$manager->isEnabled($entity->getEntityTypeId(), $entity->bundle())) {
|
||||
return;
|
||||
}
|
||||
// If we are creating a new translation we need to use the source language
|
||||
// as original language, since source values are the only ones available to
|
||||
// compare against.
|
||||
|
@ -418,8 +452,6 @@ function content_translation_entity_presave(EntityInterface $entity) {
|
|||
->getStorage($entity->entityType())->loadUnchanged($entity->id());
|
||||
}
|
||||
$langcode = $entity->language()->getId();
|
||||
/** @var \Drupal\content_translation\ContentTranslationManagerInterface $manager */
|
||||
$manager = \Drupal::service('content_translation.manager');
|
||||
$source_langcode = !$entity->original->hasTranslation($langcode) ? $manager->getTranslationMetadata($entity)->getSource() : NULL;
|
||||
\Drupal::service('content_translation.synchronizer')->synchronizeFields($entity, $langcode, $source_langcode);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
services:
|
||||
content_translation.synchronizer:
|
||||
class: Drupal\content_translation\FieldTranslationSynchronizer
|
||||
arguments: ['@entity.manager']
|
||||
arguments: ['@entity.manager', '@plugin.manager.field.field_type']
|
||||
|
||||
content_translation.subscriber:
|
||||
class: Drupal\content_translation\Routing\ContentTranslationRouteSubscriber
|
||||
|
@ -9,6 +9,12 @@ services:
|
|||
tags:
|
||||
- { name: event_subscriber }
|
||||
|
||||
content_translation.delete_access:
|
||||
class: Drupal\content_translation\Access\ContentTranslationDeleteAccess
|
||||
arguments: ['@entity_type.manager', '@content_translation.manager']
|
||||
tags:
|
||||
- { name: access_check, applies_to: _access_content_translation_delete }
|
||||
|
||||
content_translation.overview_access:
|
||||
class: Drupal\content_translation\Access\ContentTranslationOverviewAccess
|
||||
arguments: ['@entity.manager']
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
id: d6_block_translation
|
||||
label: Block translations
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Configuration
|
||||
- Multilingual
|
||||
source:
|
||||
plugin: d6_block_translation
|
||||
constants:
|
||||
dest_label: 'settings/label'
|
||||
process:
|
||||
langcode: language
|
||||
property: constants/dest_label
|
||||
translation: title
|
||||
id:
|
||||
-
|
||||
plugin: migration_lookup
|
||||
migration: d6_block
|
||||
source:
|
||||
- module
|
||||
- delta
|
||||
-
|
||||
plugin: skip_on_empty
|
||||
method: row
|
||||
plugin:
|
||||
-
|
||||
plugin: static_map
|
||||
bypass: true
|
||||
source:
|
||||
- module
|
||||
- delta
|
||||
map:
|
||||
book:
|
||||
0: book_navigation
|
||||
comment:
|
||||
0: views_block:comments_recent-block_1
|
||||
forum:
|
||||
0: forum_active_block
|
||||
1: forum_new_block
|
||||
locale:
|
||||
0: language_block
|
||||
node:
|
||||
0: node_syndicate_block
|
||||
search:
|
||||
0: search_form_block
|
||||
statistics:
|
||||
0: statistics_popular_block
|
||||
system:
|
||||
0: system_powered_by_block
|
||||
user:
|
||||
0: user_login_block
|
||||
1: system_menu_block:tools
|
||||
2: views_block:who_s_new-block_1
|
||||
3: views_block:who_s_online-who_s_online_block
|
||||
-
|
||||
plugin: block_plugin_id
|
||||
-
|
||||
plugin: skip_on_empty
|
||||
method: row
|
||||
theme:
|
||||
plugin: block_theme
|
||||
source:
|
||||
- theme
|
||||
- default_theme
|
||||
- admin_theme
|
||||
destination:
|
||||
plugin: entity:block
|
||||
migration_dependencies:
|
||||
required:
|
||||
- d6_block
|
|
@ -0,0 +1,50 @@
|
|||
id: d6_custom_block_translation
|
||||
label: Custom block translations
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Content
|
||||
- Multilingual
|
||||
source:
|
||||
plugin: d6_box_translation
|
||||
process:
|
||||
id:
|
||||
plugin: migration_lookup
|
||||
migration: d6_custom_block
|
||||
source:
|
||||
- bid
|
||||
langcode: language
|
||||
info:
|
||||
-
|
||||
plugin: callback
|
||||
source:
|
||||
- title_translated
|
||||
- title
|
||||
callable: array_filter
|
||||
-
|
||||
plugin: callback
|
||||
callable: current
|
||||
'body/value':
|
||||
-
|
||||
plugin: callback
|
||||
source:
|
||||
- body_translated
|
||||
- body
|
||||
callable: array_filter
|
||||
-
|
||||
plugin: callback
|
||||
callable: current
|
||||
'body/format':
|
||||
plugin: migration_lookup
|
||||
migration: d6_filter_format
|
||||
source: format
|
||||
destination:
|
||||
plugin: entity:block_content
|
||||
no_stub: true
|
||||
translations: true
|
||||
destination_module: content_translation
|
||||
migration_dependencies:
|
||||
required:
|
||||
- d6_filter_format
|
||||
- block_content_body_field
|
||||
- d6_custom_block
|
||||
- language
|
|
@ -0,0 +1,26 @@
|
|||
id: d6_entity_reference_translation
|
||||
label: Entity reference translations
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Multilingual
|
||||
- Follow-up migration
|
||||
deriver: Drupal\migrate_drupal\Plugin\migrate\EntityReferenceTranslationDeriver
|
||||
provider:
|
||||
- content_translation
|
||||
- migrate_drupal
|
||||
# Supported target types for entity reference translation migrations. The array
|
||||
# keys are the supported target types and the values are arrays of migrations
|
||||
# to lookup for the translated entity IDs.
|
||||
target_types:
|
||||
node:
|
||||
- d6_node_translation
|
||||
# The source plugin will be set by the deriver.
|
||||
source:
|
||||
plugin: empty
|
||||
key: default
|
||||
target: default
|
||||
# The process pipeline will be set by the deriver.
|
||||
process: []
|
||||
# The destination plugin will be set by the deriver.
|
||||
destination:
|
||||
plugin: null
|
|
@ -0,0 +1,55 @@
|
|||
id: d6_menu_links_translation
|
||||
label: Menu links
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Content
|
||||
- Multilingual
|
||||
source:
|
||||
plugin: d6_menu_link_translation
|
||||
process:
|
||||
id: mlid
|
||||
langcode: language
|
||||
title:
|
||||
-
|
||||
plugin: callback
|
||||
source:
|
||||
- title_translated
|
||||
- link_title
|
||||
callable: array_filter
|
||||
-
|
||||
plugin: callback
|
||||
callable: current
|
||||
description:
|
||||
-
|
||||
plugin: callback
|
||||
source:
|
||||
- description_translated
|
||||
- description
|
||||
callable: array_filter
|
||||
-
|
||||
plugin: callback
|
||||
callable: current
|
||||
menu_name:
|
||||
-
|
||||
plugin: migration_lookup
|
||||
# The menu migration is in the system module.
|
||||
migration: d6_menu
|
||||
source: menu_name
|
||||
-
|
||||
plugin: skip_on_empty
|
||||
method: row
|
||||
-
|
||||
plugin: static_map
|
||||
map:
|
||||
management: admin
|
||||
bypass: true
|
||||
destination:
|
||||
plugin: entity:menu_link_content
|
||||
default_bundle: menu_link_content
|
||||
no_stub: true
|
||||
translations: true
|
||||
migration_dependencies:
|
||||
required:
|
||||
- language
|
||||
- d6_menu
|
||||
- d6_menu_links
|
|
@ -0,0 +1,59 @@
|
|||
id: d6_node_translation
|
||||
label: Node translations
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- translation
|
||||
- Content
|
||||
- Multilingual
|
||||
class: Drupal\node\Plugin\migrate\D6NodeTranslation
|
||||
deriver: Drupal\node\Plugin\migrate\D6NodeDeriver
|
||||
source:
|
||||
plugin: d6_node
|
||||
translations: true
|
||||
process:
|
||||
# If you are using this file to build a custom migration consider removing
|
||||
# the nid field to allow incremental migrations.
|
||||
nid: tnid
|
||||
type: type
|
||||
langcode:
|
||||
plugin: default_value
|
||||
source: language
|
||||
default_value: "und"
|
||||
title: title
|
||||
uid: node_uid
|
||||
status: status
|
||||
created: created
|
||||
changed: changed
|
||||
promote: promote
|
||||
sticky: sticky
|
||||
'body/format':
|
||||
plugin: migration_lookup
|
||||
migration: d6_filter_format
|
||||
source: format
|
||||
'body/value': body
|
||||
'body/summary': teaser
|
||||
revision_uid: revision_uid
|
||||
revision_log: log
|
||||
revision_timestamp: timestamp
|
||||
content_translation_source: source_langcode
|
||||
|
||||
# unmapped d6 fields.
|
||||
# translate
|
||||
# moderate
|
||||
# comment
|
||||
|
||||
destination:
|
||||
plugin: entity:node
|
||||
translations: true
|
||||
destination_module: content_translation
|
||||
migration_dependencies:
|
||||
required:
|
||||
- d6_user
|
||||
- d6_node_type
|
||||
- d6_node_settings
|
||||
- d6_filter_format
|
||||
- language
|
||||
optional:
|
||||
- d6_field_instance_widget_settings
|
||||
- d6_field_formatter_settings
|
||||
- d6_upload_field_instance
|
|
@ -0,0 +1,42 @@
|
|||
id: d6_taxonomy_term_translation
|
||||
label: Taxonomy terms
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Content
|
||||
- Multilingual
|
||||
source:
|
||||
plugin: d6_taxonomy_term
|
||||
translations: true
|
||||
process:
|
||||
# If you are using this file to build a custom migration consider removing
|
||||
# the tid field to allow incremental migrations.
|
||||
tid: tid
|
||||
langcode: language
|
||||
vid:
|
||||
plugin: migration
|
||||
migration: d6_taxonomy_vocabulary
|
||||
source: vid
|
||||
name: name
|
||||
description: description
|
||||
weight: weight
|
||||
# Only attempt to stub real (non-zero) parents.
|
||||
parent_id:
|
||||
-
|
||||
plugin: skip_on_empty
|
||||
method: process
|
||||
source: parent
|
||||
-
|
||||
plugin: migration
|
||||
migration: d6_taxonomy_term
|
||||
parent:
|
||||
plugin: default_value
|
||||
default_value: 0
|
||||
source: '@parent_id'
|
||||
changed: timestamp
|
||||
destination:
|
||||
plugin: entity:taxonomy_term
|
||||
destination_module: content_translation
|
||||
migration_dependencies:
|
||||
required:
|
||||
- d6_taxonomy_vocabulary
|
||||
- d6_taxonomy_term
|
|
@ -0,0 +1,28 @@
|
|||
id: d7_comment_entity_translation
|
||||
label: Comment entity translations
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
- translation
|
||||
- Content
|
||||
class: Drupal\comment\Plugin\migrate\D7Comment
|
||||
source:
|
||||
plugin: d7_comment_entity_translation
|
||||
process:
|
||||
cid: entity_id
|
||||
subject: subject
|
||||
langcode: language
|
||||
uid: uid
|
||||
status: status
|
||||
created: created
|
||||
changed: changed
|
||||
content_translation_source: source
|
||||
content_translation_outdated: translate
|
||||
destination:
|
||||
plugin: entity:comment
|
||||
translations: true
|
||||
destination_module: content_translation
|
||||
migration_dependencies:
|
||||
required:
|
||||
- language
|
||||
- d7_entity_translation_settings
|
||||
- d7_comment
|
|
@ -0,0 +1,50 @@
|
|||
id: d7_custom_block_translation
|
||||
label: Custom block translations
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
- Content
|
||||
- Multilingual
|
||||
source:
|
||||
plugin: d7_block_custom_translation
|
||||
process:
|
||||
id:
|
||||
plugin: migration_lookup
|
||||
migration: d7_custom_block
|
||||
source:
|
||||
- bid
|
||||
langcode: language
|
||||
info:
|
||||
-
|
||||
plugin: callback
|
||||
source:
|
||||
- title_translated
|
||||
- title
|
||||
callable: array_filter
|
||||
-
|
||||
plugin: callback
|
||||
callable: current
|
||||
'body/value':
|
||||
-
|
||||
plugin: callback
|
||||
source:
|
||||
- body_translated
|
||||
- body
|
||||
callable: array_filter
|
||||
-
|
||||
plugin: callback
|
||||
callable: current
|
||||
'body/format':
|
||||
plugin: migration_lookup
|
||||
migration: d7_filter_format
|
||||
source: format
|
||||
destination:
|
||||
plugin: entity:block_content
|
||||
no_stub: true
|
||||
translations: true
|
||||
destination_module: content_translation
|
||||
migration_dependencies:
|
||||
required:
|
||||
- d7_filter_format
|
||||
- block_content_body_field
|
||||
- d7_custom_block
|
||||
- language
|
|
@ -0,0 +1,26 @@
|
|||
id: d7_entity_reference_translation
|
||||
label: Entity reference translations
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
- Multilingual
|
||||
- Follow-up migration
|
||||
deriver: Drupal\migrate_drupal\Plugin\migrate\EntityReferenceTranslationDeriver
|
||||
provider:
|
||||
- content_translation
|
||||
- migrate_drupal
|
||||
# Supported target types for entity reference translation migrations. The array
|
||||
# keys are the supported target types and the values are arrays of migrations
|
||||
# to lookup for the translated entity IDs.
|
||||
target_types:
|
||||
node:
|
||||
- d7_node_translation
|
||||
# The source plugin will be set by the deriver.
|
||||
source:
|
||||
plugin: empty
|
||||
key: default
|
||||
target: default
|
||||
# The process pipeline will be set by the deriver.
|
||||
process: []
|
||||
# The destination plugin will be set by the deriver.
|
||||
destination:
|
||||
plugin: null
|
|
@ -0,0 +1,37 @@
|
|||
id: d7_entity_translation_settings
|
||||
label: Drupal 7 Entity Translation settings
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
- Configuration
|
||||
- Multilingual
|
||||
source:
|
||||
plugin: d7_entity_translation_settings
|
||||
process:
|
||||
id: id
|
||||
target_entity_type_id: target_entity_type_id
|
||||
target_bundle: target_bundle
|
||||
default_langcode:
|
||||
plugin: static_map
|
||||
source: default_langcode
|
||||
bypass: true
|
||||
map:
|
||||
xx-et-default: site_default
|
||||
xx-et-current: current_interface
|
||||
xx-et-author: authors_default
|
||||
language_alterable: language_alterable
|
||||
third_party_settings/content_translation/enabled:
|
||||
plugin: default_value
|
||||
default_value: true
|
||||
third_party_settings/content_translation/bundle_settings/untranslatable_fields_hide: untranslatable_fields_hide
|
||||
destination:
|
||||
plugin: entity:language_content_settings
|
||||
content_translation_update_definitions:
|
||||
- comment
|
||||
- node
|
||||
- taxonomy_term
|
||||
- user
|
||||
migration_dependencies:
|
||||
optional:
|
||||
- d7_comment_type
|
||||
- d7_node_type
|
||||
- d7_taxonomy_vocabulary
|
|
@ -0,0 +1,36 @@
|
|||
id: d7_node_entity_translation
|
||||
label: Node entity translations
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
- translation
|
||||
- Content
|
||||
- Multilingual
|
||||
deriver: Drupal\node\Plugin\migrate\D7NodeDeriver
|
||||
source:
|
||||
plugin: d7_node_entity_translation
|
||||
process:
|
||||
nid: entity_id
|
||||
type: type
|
||||
langcode: language
|
||||
title: title
|
||||
uid: uid
|
||||
status: status
|
||||
created: created
|
||||
changed: changed
|
||||
promote: promote
|
||||
sticky: sticky
|
||||
revision_uid: revision_uid
|
||||
revision_log: log
|
||||
revision_timestamp: timestamp
|
||||
content_translation_source: source
|
||||
# Boolean indicating whether this translation needs to be updated.
|
||||
content_translation_outdated: translate
|
||||
destination:
|
||||
plugin: entity:node
|
||||
translations: true
|
||||
destination_module: content_translation
|
||||
migration_dependencies:
|
||||
required:
|
||||
- language
|
||||
- d7_entity_translation_settings
|
||||
- d7_node
|
|
@ -0,0 +1,45 @@
|
|||
id: d7_node_translation
|
||||
label: Node translations
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
- translation
|
||||
- Content
|
||||
- Multilingual
|
||||
class: Drupal\node\Plugin\migrate\D7NodeTranslation
|
||||
deriver: Drupal\node\Plugin\migrate\D7NodeDeriver
|
||||
source:
|
||||
plugin: d7_node
|
||||
translations: true
|
||||
process:
|
||||
# If you are using this file to build a custom migration consider removing
|
||||
# the nid field to allow incremental migrations.
|
||||
nid: tnid
|
||||
type: type
|
||||
langcode:
|
||||
plugin: default_value
|
||||
source: language
|
||||
default_value: "und"
|
||||
title: title
|
||||
uid: node_uid
|
||||
status: status
|
||||
created: created
|
||||
changed: changed
|
||||
promote: promote
|
||||
sticky: sticky
|
||||
revision_uid: revision_uid
|
||||
revision_log: log
|
||||
revision_timestamp: timestamp
|
||||
content_translation_source: source_langcode
|
||||
destination:
|
||||
plugin: entity:node
|
||||
translations: true
|
||||
content_translation_update_definitions:
|
||||
- node
|
||||
destination_module: content_translation
|
||||
migration_dependencies:
|
||||
required:
|
||||
- d7_user
|
||||
- d7_node_type
|
||||
- language
|
||||
optional:
|
||||
- d7_field_instance
|
|
@ -0,0 +1,32 @@
|
|||
id: d7_taxonomy_term_entity_translation
|
||||
label: Taxonomy term entity translations
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
- translation
|
||||
- Content
|
||||
- Multilingual
|
||||
deriver: Drupal\taxonomy\Plugin\migrate\D7TaxonomyTermDeriver
|
||||
source:
|
||||
plugin: d7_taxonomy_term_entity_translation
|
||||
process:
|
||||
tid: entity_id
|
||||
name: name
|
||||
description/value: description
|
||||
description/format: format
|
||||
langcode: language
|
||||
status: status
|
||||
content_translation_source: source
|
||||
content_translation_outdated: translate
|
||||
content_translation_uid: uid
|
||||
content_translation_created: created
|
||||
changed: changed
|
||||
forum_container: is_container
|
||||
destination:
|
||||
plugin: entity:taxonomy_term
|
||||
translations: true
|
||||
destination_module: content_translation
|
||||
migration_dependencies:
|
||||
required:
|
||||
- language
|
||||
- d7_entity_translation_settings
|
||||
- d7_taxonomy_term
|
|
@ -0,0 +1,27 @@
|
|||
id: d7_user_entity_translation
|
||||
label: User accounts entity translations
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
- translation
|
||||
- Content
|
||||
- Multilingual
|
||||
class: Drupal\user\Plugin\migrate\User
|
||||
source:
|
||||
plugin: d7_user_entity_translation
|
||||
process:
|
||||
uid: entity_id
|
||||
langcode: language
|
||||
content_translation_source: source
|
||||
content_translation_uid: uid
|
||||
content_translation_status: status
|
||||
content_translation_outdated: translate
|
||||
content_translation_created: created
|
||||
destination:
|
||||
plugin: entity:user
|
||||
translations: true
|
||||
destination_module: content_translation
|
||||
migration_dependencies:
|
||||
required:
|
||||
- language
|
||||
- d7_entity_translation_settings
|
||||
- d7_user
|
|
@ -0,0 +1,120 @@
|
|||
id: node_translation_menu_links
|
||||
label: Node Translations Menu links
|
||||
audit: true
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Drupal 7
|
||||
- Content
|
||||
- Multilingual
|
||||
source:
|
||||
plugin: menu_link
|
||||
constants:
|
||||
entity_prefix: 'entity:'
|
||||
node_prefix: 'node/'
|
||||
process:
|
||||
id: mlid
|
||||
title: link_title
|
||||
description: description
|
||||
menu_name:
|
||||
-
|
||||
plugin: migration_lookup
|
||||
# The menu migration is in the system module.
|
||||
migration:
|
||||
- d6_menu
|
||||
- d7_menu
|
||||
source: menu_name
|
||||
-
|
||||
plugin: skip_on_empty
|
||||
method: row
|
||||
-
|
||||
plugin: static_map
|
||||
map:
|
||||
management: admin
|
||||
bypass: true
|
||||
# In this process pipeline, given a menu link path that might be for a
|
||||
# translated node which has been merged with the default language node, we are
|
||||
# trying to determine the new node ID, that is the ID of the default language
|
||||
# node.
|
||||
new_nid:
|
||||
-
|
||||
# If the path is of the form "node/<ID>" and is not routed, we will get
|
||||
# back an URI of the form "base:node/<ID>".
|
||||
plugin: link_uri
|
||||
source:
|
||||
- link_path
|
||||
validate_route: false
|
||||
-
|
||||
# Isolate the node ID.
|
||||
plugin: explode
|
||||
delimiter: 'base:node/'
|
||||
-
|
||||
# Extract the node ID.
|
||||
plugin: extract
|
||||
default: false
|
||||
index:
|
||||
- 1
|
||||
-
|
||||
# Skip row if node ID is empty.
|
||||
plugin: skip_on_empty
|
||||
method: row
|
||||
-
|
||||
# With the old node ID in hand, lookup in the d6_node_translation or
|
||||
# d7_node_translation mapping tables to find the new node ID.
|
||||
plugin: migration_lookup
|
||||
migration:
|
||||
- d6_node_translation
|
||||
- d7_node_translation
|
||||
no_stub: true
|
||||
-
|
||||
# Skip row if the new node ID is empty.
|
||||
plugin: skip_on_empty
|
||||
method: row
|
||||
-
|
||||
# Extract the node ID. The migration lookup will return an array with two
|
||||
# items, the new node ID and the translation langcode. We need the node ID
|
||||
# which is at index 0.
|
||||
plugin: extract
|
||||
index:
|
||||
- 0
|
||||
# This will be used in the "link/uri" and "route" processes below.
|
||||
link_path:
|
||||
plugin: concat
|
||||
source:
|
||||
- 'constants/node_prefix'
|
||||
- '@new_nid'
|
||||
link/uri:
|
||||
plugin: concat
|
||||
source:
|
||||
- 'constants/entity_prefix'
|
||||
- '@link_path'
|
||||
link/options: options
|
||||
route:
|
||||
plugin: route
|
||||
source:
|
||||
- '@link_path'
|
||||
- options
|
||||
route_name: '@route/route_name'
|
||||
route_parameters: '@route/route_parameters'
|
||||
url: '@route/url'
|
||||
options: '@route/options'
|
||||
external: external
|
||||
weight: weight
|
||||
expanded: expanded
|
||||
enabled: enabled
|
||||
parent:
|
||||
plugin: menu_link_parent
|
||||
source:
|
||||
- plid
|
||||
- '@menu_name'
|
||||
- parent_link_path
|
||||
changed: updated
|
||||
destination:
|
||||
plugin: entity:menu_link_content
|
||||
default_bundle: menu_link_content
|
||||
no_stub: true
|
||||
migration_dependencies:
|
||||
optional:
|
||||
- d6_menu_links
|
||||
- d6_node_translation
|
||||
- d7_menu_links
|
||||
- d7_node_translation
|
|
@ -0,0 +1,35 @@
|
|||
id: statistics_node_translation_counter
|
||||
label: Node translation counter
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Drupal 7
|
||||
- Content
|
||||
- Multilingual
|
||||
source:
|
||||
plugin: node_counter
|
||||
process:
|
||||
nid:
|
||||
-
|
||||
plugin: migration_lookup
|
||||
migration:
|
||||
- d6_node_translation
|
||||
- d7_node_translation
|
||||
source: nid
|
||||
-
|
||||
plugin: skip_on_empty
|
||||
method: row
|
||||
-
|
||||
plugin: extract
|
||||
index:
|
||||
- 0
|
||||
totalcount: totalcount
|
||||
daycount: daycount
|
||||
timestamp: timestamp
|
||||
destination:
|
||||
plugin: node_counter
|
||||
migration_dependencies:
|
||||
required:
|
||||
- statistics_node_counter
|
||||
optional:
|
||||
- d6_node_translation
|
||||
- d7_node_translation
|
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_translation\Access;
|
||||
|
||||
use Drupal\content_translation\ContentTranslationManager;
|
||||
use Drupal\content_translation\ContentTranslationManagerInterface;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Routing\Access\AccessInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\language\Entity\ContentLanguageSettings;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* Access check for entity translation deletion.
|
||||
*
|
||||
* @internal This additional access checker only aims to prevent deletions in
|
||||
* pending revisions until we are able to flag revision translations as
|
||||
* deleted.
|
||||
*
|
||||
* @todo Remove this in https://www.drupal.org/node/2945956.
|
||||
*/
|
||||
class ContentTranslationDeleteAccess implements AccessInterface {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The content translation manager.
|
||||
*
|
||||
* @var \Drupal\content_translation\ContentTranslationManagerInterface
|
||||
*/
|
||||
protected $contentTranslationManager;
|
||||
|
||||
/**
|
||||
* Constructs a ContentTranslationDeleteAccess object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\content_translation\ContentTranslationManagerInterface $content_translation_manager
|
||||
* The content translation manager.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $manager, ContentTranslationManagerInterface $content_translation_manager) {
|
||||
$this->entityTypeManager = $manager;
|
||||
$this->contentTranslationManager = $content_translation_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks access to translation deletion for the specified route match.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The parameterized route.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The currently logged in account.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result.
|
||||
*/
|
||||
public function access(RouteMatchInterface $route_match, AccountInterface $account) {
|
||||
$requirement = $route_match->getRouteObject()->getRequirement('_access_content_translation_delete');
|
||||
$entity_type_id = current(explode('.', $requirement));
|
||||
$entity = $route_match->getParameter($entity_type_id);
|
||||
return $this->checkAccess($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks access to translation deletion for the specified entity.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
|
||||
* The entity translation to be deleted.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result.
|
||||
*/
|
||||
public function checkAccess(ContentEntityInterface $entity) {
|
||||
$result = AccessResult::allowed();
|
||||
|
||||
$entity_type_id = $entity->getEntityTypeId();
|
||||
$result->addCacheableDependency($entity);
|
||||
// Add the cache dependencies used by
|
||||
// ContentTranslationManager::isPendingRevisionSupportEnabled().
|
||||
if (\Drupal::moduleHandler()->moduleExists('content_moderation')) {
|
||||
foreach (Workflow::loadMultipleByType('content_moderation') as $workflow) {
|
||||
$result->addCacheableDependency($workflow);
|
||||
}
|
||||
}
|
||||
if (!ContentTranslationManager::isPendingRevisionSupportEnabled($entity_type_id, $entity->bundle())) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
if ($entity->isDefaultTranslation()) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$config = ContentLanguageSettings::load($entity_type_id . '.' . $entity->bundle());
|
||||
$result->addCacheableDependency($config);
|
||||
if (!$this->contentTranslationManager->isEnabled($entity_type_id, $entity->bundle())) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
/** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
|
||||
$storage = $this->entityTypeManager->getStorage($entity_type_id);
|
||||
$revision_id = $storage->getLatestTranslationAffectedRevisionId($entity->id(), $entity->language()->getId());
|
||||
if (!$revision_id) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $revision */
|
||||
$revision = $storage->loadRevision($revision_id);
|
||||
if ($revision->wasDefaultRevision()) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$result = $result->andIf(AccessResult::forbidden());
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
namespace Drupal\content_translation\Access;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
|
@ -90,15 +91,12 @@ class ContentTranslationManageAccessCheck implements AccessInterface {
|
|||
return AccessResult::allowed()->cachePerPermissions();
|
||||
}
|
||||
|
||||
/* @var \Drupal\content_translation\ContentTranslationHandlerInterface $handler */
|
||||
$handler = $this->entityManager->getHandler($entity->getEntityTypeId(), 'translation');
|
||||
|
||||
// Load translation.
|
||||
$translations = $entity->getTranslationLanguages();
|
||||
$languages = $this->languageManager->getLanguages();
|
||||
|
||||
switch ($operation) {
|
||||
case 'create':
|
||||
/* @var \Drupal\content_translation\ContentTranslationHandlerInterface $handler */
|
||||
$handler = $this->entityManager->getHandler($entity->getEntityTypeId(), 'translation');
|
||||
$translations = $entity->getTranslationLanguages();
|
||||
$languages = $this->languageManager->getLanguages();
|
||||
$source_language = $this->languageManager->getLanguage($source) ?: $entity->language();
|
||||
$target_language = $this->languageManager->getLanguage($target) ?: $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT);
|
||||
$is_new_translation = ($source_language->getId() != $target_language->getId()
|
||||
|
@ -109,12 +107,14 @@ class ContentTranslationManageAccessCheck implements AccessInterface {
|
|||
->andIf($handler->getTranslationAccess($entity, $operation));
|
||||
|
||||
case 'delete':
|
||||
// @todo Remove this in https://www.drupal.org/node/2945956.
|
||||
/** @var \Drupal\Core\Access\AccessResultInterface $delete_access */
|
||||
$delete_access = \Drupal::service('content_translation.delete_access')->checkAccess($entity);
|
||||
$access = $this->checkAccess($entity, $language, $operation);
|
||||
return $delete_access->andIf($access);
|
||||
|
||||
case 'update':
|
||||
$has_translation = isset($languages[$language->getId()])
|
||||
&& $language->getId() != $entity->getUntranslated()->language()->getId()
|
||||
&& isset($translations[$language->getId()]);
|
||||
return AccessResult::allowedIf($has_translation)->cachePerPermissions()->addCacheableDependency($entity)
|
||||
->andIf($handler->getTranslationAccess($entity, $operation));
|
||||
return $this->checkAccess($entity, $language, $operation);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,4 +122,30 @@ class ContentTranslationManageAccessCheck implements AccessInterface {
|
|||
return AccessResult::neutral();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs access checks for the specified operation.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
|
||||
* The entity being checked.
|
||||
* @param \Drupal\Core\Language\LanguageInterface $language
|
||||
* For an update or delete operation, the language code of the translation
|
||||
* being updated or deleted.
|
||||
* @param string $operation
|
||||
* The operation to be checked.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* An access result object.
|
||||
*/
|
||||
protected function checkAccess(ContentEntityInterface $entity, LanguageInterface $language, $operation) {
|
||||
/* @var \Drupal\content_translation\ContentTranslationHandlerInterface $handler */
|
||||
$handler = $this->entityManager->getHandler($entity->getEntityTypeId(), 'translation');
|
||||
$translations = $entity->getTranslationLanguages();
|
||||
$languages = $this->languageManager->getLanguages();
|
||||
$has_translation = isset($languages[$language->getId()])
|
||||
&& $language->getId() != $entity->getUntranslated()->language()->getId()
|
||||
&& isset($translations[$language->getId()]);
|
||||
return AccessResult::allowedIf($has_translation)->cachePerPermissions()->addCacheableDependency($entity)
|
||||
->andIf($handler->getTranslationAccess($entity, $operation));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_translation;
|
||||
|
||||
/**
|
||||
* Interface providing support for content translation bundle settings.
|
||||
*/
|
||||
interface BundleTranslationSettingsInterface {
|
||||
|
||||
/**
|
||||
* Returns translation settings for the specified bundle.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type identifier.
|
||||
* @param string $bundle
|
||||
* The bundle name.
|
||||
*
|
||||
* @return array
|
||||
* An associative array of values keyed by setting name.
|
||||
*/
|
||||
public function getBundleTranslationSettings($entity_type_id, $bundle);
|
||||
|
||||
/**
|
||||
* Sets translation settings for the specified bundle.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type identifier.
|
||||
* @param string $bundle
|
||||
* The bundle name.
|
||||
* @param array $settings
|
||||
* An associative array of values keyed by setting name.
|
||||
*/
|
||||
public function setBundleTranslationSettings($entity_type_id, $bundle, array $settings);
|
||||
|
||||
}
|
|
@ -5,6 +5,7 @@ namespace Drupal\content_translation;
|
|||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
|
||||
use Drupal\Core\Entity\EntityChangedInterface;
|
||||
use Drupal\Core\Entity\EntityChangesDetectionTrait;
|
||||
use Drupal\Core\Entity\EntityHandlerInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
|
@ -13,8 +14,10 @@ use Drupal\Core\Field\BaseFieldDefinition;
|
|||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\Core\Messenger\MessengerInterface;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\user\Entity\User;
|
||||
use Drupal\user\EntityOwnerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
@ -25,7 +28,10 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
|
|||
* @ingroup entity_api
|
||||
*/
|
||||
class ContentTranslationHandler implements ContentTranslationHandlerInterface, EntityHandlerInterface {
|
||||
|
||||
use EntityChangesDetectionTrait;
|
||||
use DependencySerializationTrait;
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The type of the entity being translated.
|
||||
|
@ -55,6 +61,13 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
|
|||
*/
|
||||
protected $manager;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
|
@ -70,6 +83,13 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
|
|||
*/
|
||||
protected $fieldStorageDefinitions;
|
||||
|
||||
/**
|
||||
* The messenger service.
|
||||
*
|
||||
* @var \Drupal\Core\Messenger\MessengerInterface
|
||||
*/
|
||||
protected $messenger;
|
||||
|
||||
/**
|
||||
* Initializes an instance of the content translation controller.
|
||||
*
|
||||
|
@ -83,14 +103,18 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
|
|||
* The entity manager.
|
||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||
* The current user.
|
||||
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
|
||||
* The messenger service.
|
||||
*/
|
||||
public function __construct(EntityTypeInterface $entity_type, LanguageManagerInterface $language_manager, ContentTranslationManagerInterface $manager, EntityManagerInterface $entity_manager, AccountInterface $current_user) {
|
||||
public function __construct(EntityTypeInterface $entity_type, LanguageManagerInterface $language_manager, ContentTranslationManagerInterface $manager, EntityManagerInterface $entity_manager, AccountInterface $current_user, MessengerInterface $messenger) {
|
||||
$this->entityTypeId = $entity_type->id();
|
||||
$this->entityType = $entity_type;
|
||||
$this->languageManager = $language_manager;
|
||||
$this->manager = $manager;
|
||||
$this->entityTypeManager = $entity_manager;
|
||||
$this->currentUser = $current_user;
|
||||
$this->fieldStorageDefinitions = $entity_manager->getLastInstalledFieldStorageDefinitions($this->entityTypeId);
|
||||
$this->messenger = $messenger;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -102,7 +126,8 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
|
|||
$container->get('language_manager'),
|
||||
$container->get('content_translation.manager'),
|
||||
$container->get('entity.manager'),
|
||||
$container->get('current_user')
|
||||
$container->get('current_user'),
|
||||
$container->get('messenger')
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -116,6 +141,7 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
|
|||
->setLabel(t('Translation source'))
|
||||
->setDescription(t('The source language from which this translation was created.'))
|
||||
->setDefaultValue(LanguageInterface::LANGCODE_NOT_SPECIFIED)
|
||||
->setInitialValue(LanguageInterface::LANGCODE_NOT_SPECIFIED)
|
||||
->setRevisionable(TRUE)
|
||||
->setTranslatable(TRUE);
|
||||
|
||||
|
@ -123,6 +149,7 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
|
|||
->setLabel(t('Translation outdated'))
|
||||
->setDescription(t('A boolean indicating whether this translation needs to be updated.'))
|
||||
->setDefaultValue(FALSE)
|
||||
->setInitialValue(FALSE)
|
||||
->setRevisionable(TRUE)
|
||||
->setTranslatable(TRUE);
|
||||
|
||||
|
@ -142,6 +169,7 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
|
|||
->setLabel(t('Translation status'))
|
||||
->setDescription(t('A boolean indicating whether the translation is visible to non-translators.'))
|
||||
->setDefaultValue(TRUE)
|
||||
->setInitialValue(TRUE)
|
||||
->setRevisionable(TRUE)
|
||||
->setTranslatable(TRUE);
|
||||
}
|
||||
|
@ -266,6 +294,8 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function entityFormAlter(array &$form, FormStateInterface $form_state, EntityInterface $entity) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
|
||||
$form_object = $form_state->getFormObject();
|
||||
$form_langcode = $form_object->getFormLangcode($form_state);
|
||||
$entity_langcode = $entity->getUntranslated()->language()->getId();
|
||||
|
@ -360,7 +390,12 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
|
|||
break;
|
||||
}
|
||||
}
|
||||
$access = $this->getTranslationAccess($entity, 'delete')->isAllowed() || ($entity->access('delete') && $this->entityType->hasLinkTemplate('delete-form'));
|
||||
/** @var \Drupal\Core\Access\AccessResultInterface $delete_access */
|
||||
$delete_access = \Drupal::service('content_translation.delete_access')->checkAccess($entity);
|
||||
$access = $delete_access->isAllowed() && (
|
||||
$this->getTranslationAccess($entity, 'delete')->isAllowed() ||
|
||||
($entity->access('delete') && $this->entityType->hasLinkTemplate('delete-form'))
|
||||
);
|
||||
$form['actions']['delete_translation'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Delete translation'),
|
||||
|
@ -423,12 +458,19 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
|
|||
];
|
||||
|
||||
$translate = !$new_translation && $metadata->isOutdated();
|
||||
if (!$translate) {
|
||||
$outdated_access = !ContentTranslationManager::isPendingRevisionSupportEnabled($entity->getEntityTypeId(), $entity->bundle());
|
||||
if (!$outdated_access) {
|
||||
$form['content_translation']['outdated'] = [
|
||||
'#markup' => $this->t('Translations cannot be flagged as outdated when content is moderated.'),
|
||||
];
|
||||
}
|
||||
elseif (!$translate) {
|
||||
$form['content_translation']['retranslate'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Flag other translations as outdated'),
|
||||
'#default_value' => FALSE,
|
||||
'#description' => t('If you made a significant change, which means the other translations should be updated, you can flag all translations of this content as outdated. This will not change any other property of them, like whether they are published or not.'),
|
||||
'#access' => $outdated_access,
|
||||
];
|
||||
}
|
||||
else {
|
||||
|
@ -437,6 +479,7 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
|
|||
'#title' => t('This translation needs to be updated'),
|
||||
'#default_value' => $translate,
|
||||
'#description' => t('When this option is checked, this translation needs to be updated. Uncheck when the translation is up to date again.'),
|
||||
'#access' => $outdated_access,
|
||||
];
|
||||
$form['content_translation']['#open'] = TRUE;
|
||||
}
|
||||
|
@ -469,10 +512,6 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
|
|||
'#default_value' => $new_translation || !$date ? '' : format_date($date, 'custom', 'Y-m-d H:i:s O'),
|
||||
];
|
||||
|
||||
if (isset($language_widget)) {
|
||||
$language_widget['#multilingual'] = TRUE;
|
||||
}
|
||||
|
||||
$form['#process'][] = [$this, 'entityFormSharedElements'];
|
||||
}
|
||||
|
||||
|
@ -509,6 +548,20 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
|
|||
$ignored_types = array_flip(['actions', 'value', 'hidden', 'vertical_tabs', 'token', 'details']);
|
||||
}
|
||||
|
||||
/** @var \Drupal\Core\Entity\ContentEntityForm $form_object */
|
||||
$form_object = $form_state->getFormObject();
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
$entity = $form_object->getEntity();
|
||||
$display_translatability_clue = !$entity->isDefaultTranslationAffectedOnly();
|
||||
$hide_untranslatable_fields = $entity->isDefaultTranslationAffectedOnly() && !$entity->isDefaultTranslation();
|
||||
$translation_form = $form_state->get(['content_translation', 'translation_form']);
|
||||
$display_warning = FALSE;
|
||||
|
||||
// We use field definitions to identify untranslatable field widgets to be
|
||||
// hidden. Fields that are not involved in translation changes checks should
|
||||
// not be affected by this logic (the "revision_log" field, for instance).
|
||||
$field_definitions = array_diff_key($entity->getFieldDefinitions(), array_flip($this->getFieldsToSkipFromTranslationChangesCheck($entity)));
|
||||
|
||||
foreach (Element::children($element) as $key) {
|
||||
if (!isset($element[$key]['#type'])) {
|
||||
$this->entityFormSharedElements($element[$key], $form_state, $form);
|
||||
|
@ -521,10 +574,17 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
|
|||
// Elements are considered to be non multilingual by default.
|
||||
if (empty($element[$key]['#multilingual'])) {
|
||||
// If we are displaying a multilingual entity form we need to provide
|
||||
// translatability clues, otherwise the shared form elements should be
|
||||
// hidden.
|
||||
if (!$form_state->get(['content_translation', 'translation_form'])) {
|
||||
$this->addTranslatabilityClue($element[$key]);
|
||||
// translatability clues, otherwise the non-multilingual form elements
|
||||
// should be hidden.
|
||||
if (!$translation_form) {
|
||||
if ($display_translatability_clue) {
|
||||
$this->addTranslatabilityClue($element[$key]);
|
||||
}
|
||||
// Hide widgets for untranslatable fields.
|
||||
if ($hide_untranslatable_fields && isset($field_definitions[$key])) {
|
||||
$element[$key]['#access'] = FALSE;
|
||||
$display_warning = TRUE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$element[$key]['#access'] = FALSE;
|
||||
|
@ -533,6 +593,11 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
|
|||
}
|
||||
}
|
||||
|
||||
if ($display_warning && !$form_state->isSubmitted() && !$form_state->isRebuilding()) {
|
||||
$url = $entity->getUntranslated()->toUrl('edit-form')->toString();
|
||||
$this->messenger->addWarning($this->t('Fields that apply to all languages are hidden to avoid conflicting changes. <a href=":url">Edit them on the original language form</a>.', [':url' => $url]));
|
||||
}
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
|
@ -640,7 +705,7 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
|
|||
// after the entity has been validated, so that it does not break the
|
||||
// EntityChanged constraint validator. The content translation metadata
|
||||
// field for the changed timestamp does not have such a constraint defined
|
||||
// at the moment, but it is correct to update it's value in a submission
|
||||
// at the moment, but it is correct to update its value in a submission
|
||||
// handler as well and have the same logic like in the Form API.
|
||||
if ($entity->hasField('content_translation_changed')) {
|
||||
$metadata = $this->manager->getTranslationMetadata($entity);
|
||||
|
@ -665,7 +730,7 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
|
|||
'target' => $form_object->getFormLangcode($form_state),
|
||||
]);
|
||||
$languages = $this->languageManager->getLanguages();
|
||||
drupal_set_message(t('Source language set to: %language', ['%language' => $languages[$source]->getName()]));
|
||||
$this->messenger->addStatus(t('Source language set to: %language', ['%language' => $languages[$source]->getName()]));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -674,10 +739,10 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
|
|||
* Takes care of entity deletion.
|
||||
*/
|
||||
public function entityFormDelete($form, FormStateInterface $form_state) {
|
||||
$form_object = $form_state->getFormObject()->getEntity();
|
||||
$form_object = $form_state->getFormObject();
|
||||
$entity = $form_object->getEntity();
|
||||
if (count($entity->getTranslationLanguages()) > 1) {
|
||||
drupal_set_message(t('This will delete all the translations of %label.', ['%label' => $entity->label()]), 'warning');
|
||||
$this->messenger->addWarning(t('This will delete all the translations of %label.', ['%label' => $entity->label()]));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,11 +4,12 @@ namespace Drupal\content_translation;
|
|||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* Provides common functionality for content translation.
|
||||
*/
|
||||
class ContentTranslationManager implements ContentTranslationManagerInterface {
|
||||
class ContentTranslationManager implements ContentTranslationManagerInterface, BundleTranslationSettingsInterface {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
|
@ -105,6 +106,23 @@ class ContentTranslationManager implements ContentTranslationManagerInterface {
|
|||
return $enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setBundleTranslationSettings($entity_type_id, $bundle, array $settings) {
|
||||
$config = $this->loadContentLanguageSettings($entity_type_id, $bundle);
|
||||
$config->setThirdPartySetting('content_translation', 'bundle_settings', $settings)
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getBundleTranslationSettings($entity_type_id, $bundle) {
|
||||
$config = $this->loadContentLanguageSettings($entity_type_id, $bundle);
|
||||
return $config->getThirdPartySetting('content_translation', 'bundle_settings', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a content language config entity based on the entity type and bundle.
|
||||
*
|
||||
|
@ -128,4 +146,47 @@ class ContentTranslationManager implements ContentTranslationManagerInterface {
|
|||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether support for pending revisions should be enabled.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The ID of the entity type to be checked.
|
||||
* @param string $bundle_id
|
||||
* (optional) The ID of the bundle to be checked. Defaults to none.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if pending revisions should be enabled, FALSE otherwise.
|
||||
*
|
||||
* @internal
|
||||
* There is ongoing discussion about how pending revisions should behave.
|
||||
* The logic enabling pending revision support is likely to change once a
|
||||
* decision is made.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2940575
|
||||
*/
|
||||
public static function isPendingRevisionSupportEnabled($entity_type_id, $bundle_id = NULL) {
|
||||
if (!\Drupal::moduleHandler()->moduleExists('content_moderation')) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
foreach (Workflow::loadMultipleByType('content_moderation') as $workflow) {
|
||||
/** @var \Drupal\content_moderation\Plugin\WorkflowType\ContentModeration $plugin */
|
||||
$plugin = $workflow->getTypePlugin();
|
||||
$entity_type_ids = array_flip($plugin->getEntityTypes());
|
||||
if (isset($entity_type_ids[$entity_type_id])) {
|
||||
if (!isset($bundle_id)) {
|
||||
return TRUE;
|
||||
}
|
||||
else {
|
||||
$bundle_ids = array_flip($plugin->getBundlesForEntityType($entity_type_id));
|
||||
if (isset($bundle_ids[$bundle_id])) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ class ContentTranslationMetadataWrapper implements ContentTranslationMetadataWra
|
|||
/**
|
||||
* Initializes an instance of the content translation metadata handler.
|
||||
*
|
||||
* @param EntityInterface $translation
|
||||
* @param \Drupal\Core\Entity\EntityInterface $translation
|
||||
* The entity translation to be wrapped.
|
||||
* @param ContentTranslationHandlerInterface $handler
|
||||
* The content translation handler.
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Drupal\content_translation\Controller;
|
||||
|
||||
use Drupal\content_translation\ContentTranslationManager;
|
||||
use Drupal\content_translation\ContentTranslationManagerInterface;
|
||||
use Drupal\Core\Cache\CacheableMetadata;
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
|
@ -87,6 +88,7 @@ class ContentTranslationController extends ControllerBase {
|
|||
$handler = $this->entityManager()->getHandler($entity_type_id, 'translation');
|
||||
$manager = $this->manager;
|
||||
$entity_type = $entity->getEntityType();
|
||||
$use_latest_revisions = $entity_type->isRevisionable() && ContentTranslationManager::isPendingRevisionSupportEnabled($entity_type_id, $entity->bundle());
|
||||
|
||||
// Start collecting the cacheability metadata, starting with the entity and
|
||||
// later merge in the access result cacheability metadata.
|
||||
|
@ -99,6 +101,9 @@ class ContentTranslationController extends ControllerBase {
|
|||
|
||||
$rows = [];
|
||||
$show_source_column = FALSE;
|
||||
/** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
|
||||
$storage = $this->entityTypeManager()->getStorage($entity_type_id);
|
||||
$default_revision = $storage->load($entity->id());
|
||||
|
||||
if ($this->languageManager()->isMultilingual()) {
|
||||
// Determine whether the current entity is translatable.
|
||||
|
@ -121,37 +126,33 @@ class ContentTranslationController extends ControllerBase {
|
|||
$language_name = $language->getName();
|
||||
$langcode = $language->getId();
|
||||
|
||||
$add_url = new Url(
|
||||
"entity.$entity_type_id.content_translation_add",
|
||||
[
|
||||
'source' => $original,
|
||||
'target' => $language->getId(),
|
||||
$entity_type_id => $entity->id(),
|
||||
],
|
||||
[
|
||||
'language' => $language,
|
||||
]
|
||||
);
|
||||
$edit_url = new Url(
|
||||
"entity.$entity_type_id.content_translation_edit",
|
||||
[
|
||||
'language' => $language->getId(),
|
||||
$entity_type_id => $entity->id(),
|
||||
],
|
||||
[
|
||||
'language' => $language,
|
||||
]
|
||||
);
|
||||
$delete_url = new Url(
|
||||
"entity.$entity_type_id.content_translation_delete",
|
||||
[
|
||||
'language' => $language->getId(),
|
||||
$entity_type_id => $entity->id(),
|
||||
],
|
||||
[
|
||||
'language' => $language,
|
||||
]
|
||||
);
|
||||
// If the entity type is revisionable, we may have pending revisions
|
||||
// with translations not available yet in the default revision. Thus we
|
||||
// need to load the latest translation-affecting revision for each
|
||||
// language to be sure we are listing all available translations.
|
||||
if ($use_latest_revisions) {
|
||||
$entity = $default_revision;
|
||||
$latest_revision_id = $storage->getLatestTranslationAffectedRevisionId($entity->id(), $langcode);
|
||||
if ($latest_revision_id) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $latest_revision */
|
||||
$latest_revision = $storage->loadRevision($latest_revision_id);
|
||||
// Make sure we do not list removed translations, i.e. translations
|
||||
// that have been part of a default revision but no longer are.
|
||||
if (!$latest_revision->wasDefaultRevision() || $default_revision->hasTranslation($langcode)) {
|
||||
$entity = $latest_revision;
|
||||
}
|
||||
}
|
||||
$translations = $entity->getTranslationLanguages();
|
||||
}
|
||||
|
||||
$options = ['language' => $language];
|
||||
$add_url = $entity->toUrl('drupal:content-translation-add', $options)
|
||||
->setRouteParameter('source', $original)
|
||||
->setRouteParameter('target', $language->getId());
|
||||
$edit_url = $entity->toUrl('drupal:content-translation-edit', $options)
|
||||
->setRouteParameter('language', $language->getId());
|
||||
$delete_url = $entity->toUrl('drupal:content-translation-delete', $options)
|
||||
->setRouteParameter('language', $language->getId());
|
||||
$operations = [
|
||||
'data' => [
|
||||
'#type' => 'operations',
|
||||
|
@ -196,38 +197,50 @@ class ContentTranslationController extends ControllerBase {
|
|||
if (isset($links['edit'])) {
|
||||
$links['edit']['title'] = $this->t('Edit');
|
||||
}
|
||||
$status = ['data' => [
|
||||
'#type' => 'inline_template',
|
||||
'#template' => '<span class="status">{% if status %}{{ "Published"|t }}{% else %}{{ "Not published"|t }}{% endif %}</span>{% if outdated %} <span class="marker">{{ "outdated"|t }}</span>{% endif %}',
|
||||
'#context' => [
|
||||
'status' => $metadata->isPublished(),
|
||||
'outdated' => $metadata->isOutdated(),
|
||||
$status = [
|
||||
'data' => [
|
||||
'#type' => 'inline_template',
|
||||
'#template' => '<span class="status">{% if status %}{{ "Published"|t }}{% else %}{{ "Not published"|t }}{% endif %}</span>{% if outdated %} <span class="marker">{{ "outdated"|t }}</span>{% endif %}',
|
||||
'#context' => [
|
||||
'status' => $metadata->isPublished(),
|
||||
'outdated' => $metadata->isOutdated(),
|
||||
],
|
||||
],
|
||||
]];
|
||||
];
|
||||
|
||||
if ($is_original) {
|
||||
$language_name = $this->t('<strong>@language_name (Original language)</strong>', ['@language_name' => $language_name]);
|
||||
$source_name = $this->t('n/a');
|
||||
}
|
||||
else {
|
||||
$source_name = isset($languages[$source]) ? $languages[$source]->getName() : $this->t('n/a');
|
||||
$delete_access = $entity->access('delete', NULL, TRUE);
|
||||
$translation_access = $handler->getTranslationAccess($entity, 'delete');
|
||||
$cacheability = $cacheability
|
||||
->merge(CacheableMetadata::createFromObject($delete_access))
|
||||
->merge(CacheableMetadata::createFromObject($translation_access));
|
||||
if ($entity->access('delete') && $entity_type->hasLinkTemplate('delete-form')) {
|
||||
$links['delete'] = [
|
||||
'title' => $this->t('Delete'),
|
||||
'url' => $entity->urlInfo('delete-form'),
|
||||
'language' => $language,
|
||||
];
|
||||
/** @var \Drupal\Core\Access\AccessResultInterface $delete_route_access */
|
||||
$delete_route_access = \Drupal::service('content_translation.delete_access')->checkAccess($translation);
|
||||
$cacheability->addCacheableDependency($delete_route_access);
|
||||
|
||||
if ($delete_route_access->isAllowed()) {
|
||||
$source_name = isset($languages[$source]) ? $languages[$source]->getName() : $this->t('n/a');
|
||||
$delete_access = $entity->access('delete', NULL, TRUE);
|
||||
$translation_access = $handler->getTranslationAccess($entity, 'delete');
|
||||
$cacheability
|
||||
->addCacheableDependency($delete_access)
|
||||
->addCacheableDependency($translation_access);
|
||||
|
||||
if ($delete_access->isAllowed() && $entity_type->hasLinkTemplate('delete-form')) {
|
||||
$links['delete'] = [
|
||||
'title' => $this->t('Delete'),
|
||||
'url' => $entity->urlInfo('delete-form'),
|
||||
'language' => $language,
|
||||
];
|
||||
}
|
||||
elseif ($translation_access->isAllowed()) {
|
||||
$links['delete'] = [
|
||||
'title' => $this->t('Delete'),
|
||||
'url' => $delete_url,
|
||||
];
|
||||
}
|
||||
}
|
||||
elseif ($translation_access->isAllowed()) {
|
||||
$links['delete'] = [
|
||||
'title' => $this->t('Delete'),
|
||||
'url' => $delete_url,
|
||||
];
|
||||
else {
|
||||
$this->messenger()->addWarning($this->t('The "Delete translation" action is only available for published translations.'), FALSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -328,8 +341,21 @@ class ContentTranslationController extends ControllerBase {
|
|||
* A processed form array ready to be rendered.
|
||||
*/
|
||||
public function add(LanguageInterface $source, LanguageInterface $target, RouteMatchInterface $route_match, $entity_type_id = NULL) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
$entity = $route_match->getParameter($entity_type_id);
|
||||
|
||||
// In case of a pending revision, make sure we load the latest
|
||||
// translation-affecting revision for the source language, otherwise the
|
||||
// initial form values may not be up-to-date.
|
||||
if (!$entity->isDefaultRevision() && ContentTranslationManager::isPendingRevisionSupportEnabled($entity_type_id, $entity->bundle())) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
|
||||
$storage = $this->entityTypeManager()->getStorage($entity->getEntityTypeId());
|
||||
$revision_id = $storage->getLatestTranslationAffectedRevisionId($entity->id(), $source->getId());
|
||||
if ($revision_id != $entity->getRevisionId()) {
|
||||
$entity = $storage->loadRevision($revision_id);
|
||||
}
|
||||
}
|
||||
|
||||
// @todo Exploit the upcoming hook_entity_prepare() when available.
|
||||
// See https://www.drupal.org/node/1810394.
|
||||
$this->prepareTranslation($entity, $source, $target);
|
||||
|
|
|
@ -5,6 +5,8 @@ namespace Drupal\content_translation;
|
|||
use Drupal\Core\Config\Entity\ThirdPartySettingsInterface;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\FieldTypePluginManagerInterface;
|
||||
|
||||
/**
|
||||
* Provides field translation synchronization capabilities.
|
||||
|
@ -18,14 +20,57 @@ class FieldTranslationSynchronizer implements FieldTranslationSynchronizerInterf
|
|||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* The field type plugin manager.
|
||||
*
|
||||
* @var \Drupal\Core\Field\FieldTypePluginManagerInterface
|
||||
*/
|
||||
protected $fieldTypeManager;
|
||||
|
||||
/**
|
||||
* Constructs a FieldTranslationSynchronizer object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entityManager
|
||||
* The entity manager.
|
||||
* @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager
|
||||
* The field type plugin manager.
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $entityManager) {
|
||||
public function __construct(EntityManagerInterface $entityManager, FieldTypePluginManagerInterface $field_type_manager) {
|
||||
$this->entityManager = $entityManager;
|
||||
$this->fieldTypeManager = $field_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFieldSynchronizedProperties(FieldDefinitionInterface $field_definition) {
|
||||
$properties = [];
|
||||
$settings = $this->getFieldSynchronizationSettings($field_definition);
|
||||
foreach ($settings as $group => $translatable) {
|
||||
if (!$translatable) {
|
||||
$field_type_definition = $this->fieldTypeManager->getDefinition($field_definition->getType());
|
||||
if (!empty($field_type_definition['column_groups'][$group]['columns'])) {
|
||||
$properties = array_merge($properties, $field_type_definition['column_groups'][$group]['columns']);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the synchronization settings for the specified field.
|
||||
*
|
||||
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
|
||||
* A field definition.
|
||||
*
|
||||
* @return string[]
|
||||
* An array of synchronized field property names.
|
||||
*/
|
||||
protected function getFieldSynchronizationSettings(FieldDefinitionInterface $field_definition) {
|
||||
if ($field_definition instanceof ThirdPartySettingsInterface && $field_definition->isTranslatable()) {
|
||||
return $field_definition->getThirdPartySetting('content_translation', 'translation_sync', []);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -33,7 +78,6 @@ class FieldTranslationSynchronizer implements FieldTranslationSynchronizerInterf
|
|||
*/
|
||||
public function synchronizeFields(ContentEntityInterface $entity, $sync_langcode, $original_langcode = NULL) {
|
||||
$translations = $entity->getTranslationLanguages();
|
||||
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
|
||||
|
||||
// If we have no information about what to sync to, if we are creating a new
|
||||
// entity, if we have no translations for the current entity and we are not
|
||||
|
@ -43,21 +87,55 @@ class FieldTranslationSynchronizer implements FieldTranslationSynchronizerInterf
|
|||
}
|
||||
|
||||
// If the entity language is being changed there is nothing to synchronize.
|
||||
$entity_type = $entity->getEntityTypeId();
|
||||
$entity_unchanged = isset($entity->original) ? $entity->original : $this->entityManager->getStorage($entity_type)->loadUnchanged($entity->id());
|
||||
$entity_unchanged = $this->getOriginalEntity($entity);
|
||||
if ($entity->getUntranslated()->language()->getId() != $entity_unchanged->getUntranslated()->language()->getId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($entity->isNewRevision()) {
|
||||
if ($entity->isDefaultTranslationAffectedOnly()) {
|
||||
// If changes to untranslatable fields are configured to affect only the
|
||||
// default translation, we need to skip synchronization in pending
|
||||
// revisions, otherwise multiple translations would be affected.
|
||||
if (!$entity->isDefaultRevision()) {
|
||||
return;
|
||||
}
|
||||
// When this mode is enabled, changes to synchronized properties are
|
||||
// allowed only in the default translation, thus we need to make sure this
|
||||
// is always used as source for the synchronization process.
|
||||
else {
|
||||
$sync_langcode = $entity->getUntranslated()->language()->getId();
|
||||
}
|
||||
}
|
||||
elseif ($entity->isDefaultRevision()) {
|
||||
// If a new default revision is being saved, but a newer default
|
||||
// revision was created meanwhile, use any other translation as source
|
||||
// for synchronization, since that will have been merged from the
|
||||
// default revision. In this case the actual language does not matter as
|
||||
// synchronized properties are the same for all the translations in the
|
||||
// default revision.
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $default_revision */
|
||||
$default_revision = $this->entityManager
|
||||
->getStorage($entity->getEntityTypeId())
|
||||
->load($entity->id());
|
||||
if ($default_revision->getLoadedRevisionId() !== $entity->getLoadedRevisionId()) {
|
||||
$other_langcodes = array_diff_key($default_revision->getTranslationLanguages(), [$sync_langcode => FALSE]);
|
||||
if ($other_langcodes) {
|
||||
$sync_langcode = key($other_langcodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @var \Drupal\Core\Field\FieldItemListInterface $items */
|
||||
foreach ($entity as $field_name => $items) {
|
||||
$field_definition = $items->getFieldDefinition();
|
||||
$field_type_definition = $field_type_manager->getDefinition($field_definition->getType());
|
||||
$field_type_definition = $this->fieldTypeManager->getDefinition($field_definition->getType());
|
||||
$column_groups = $field_type_definition['column_groups'];
|
||||
|
||||
// Sync if the field is translatable, not empty, and the synchronization
|
||||
// setting is enabled.
|
||||
if ($field_definition instanceof ThirdPartySettingsInterface && $field_definition->isTranslatable() && !$items->isEmpty() && $translation_sync = $field_definition->getThirdPartySetting('content_translation', 'translation_sync')) {
|
||||
if (($translation_sync = $this->getFieldSynchronizationSettings($field_definition)) && !$items->isEmpty()) {
|
||||
// Retrieve all the untranslatable column groups and merge them into
|
||||
// single list.
|
||||
$groups = array_keys(array_diff($translation_sync, array_filter($translation_sync)));
|
||||
|
@ -101,10 +179,30 @@ class FieldTranslationSynchronizer implements FieldTranslationSynchronizerInterf
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the original unchanged entity to be used to detect changes.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
|
||||
* The entity being changed.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\ContentEntityInterface
|
||||
* The unchanged entity.
|
||||
*/
|
||||
protected function getOriginalEntity(ContentEntityInterface $entity) {
|
||||
if (!isset($entity->original)) {
|
||||
$storage = $this->entityManager->getStorage($entity->getEntityTypeId());
|
||||
$original = $entity->isDefaultRevision() ? $storage->loadUnchanged($entity->id()) : $storage->loadRevision($entity->getLoadedRevisionId());
|
||||
}
|
||||
else {
|
||||
$original = $entity->original;
|
||||
}
|
||||
return $original;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function synchronizeItems(array &$values, array $unchanged_items, $sync_langcode, array $translations, array $columns) {
|
||||
public function synchronizeItems(array &$values, array $unchanged_items, $sync_langcode, array $translations, array $properties) {
|
||||
$source_items = $values[$sync_langcode];
|
||||
|
||||
// Make sure we can detect any change in the source items.
|
||||
|
@ -120,7 +218,7 @@ class FieldTranslationSynchronizer implements FieldTranslationSynchronizerInterf
|
|||
// for each column.
|
||||
for ($delta = 0; $delta < $total; $delta++) {
|
||||
foreach (['old' => $unchanged_items, 'new' => $source_items] as $key => $items) {
|
||||
if ($item_id = $this->itemHash($items, $delta, $columns)) {
|
||||
if ($item_id = $this->itemHash($items, $delta, $properties)) {
|
||||
$change_map[$item_id][$key][] = $delta;
|
||||
}
|
||||
}
|
||||
|
@ -153,7 +251,7 @@ class FieldTranslationSynchronizer implements FieldTranslationSynchronizerInterf
|
|||
$old_delta = NULL;
|
||||
$new_delta = NULL;
|
||||
|
||||
if ($item_id = $this->itemHash($source_items, $delta, $columns)) {
|
||||
if ($item_id = $this->itemHash($source_items, $delta, $properties)) {
|
||||
if (!empty($change_map[$item_id]['old'])) {
|
||||
$old_delta = array_shift($change_map[$item_id]['old']);
|
||||
}
|
||||
|
@ -174,9 +272,7 @@ class FieldTranslationSynchronizer implements FieldTranslationSynchronizerInterf
|
|||
// items and the other columns from the existing values. This only
|
||||
// works if the delta exists in the language.
|
||||
elseif ($created && !empty($original_field_values[$langcode][$delta])) {
|
||||
$item_columns_to_sync = array_intersect_key($source_items[$delta], array_flip($columns));
|
||||
$item_columns_to_keep = array_diff_key($original_field_values[$langcode][$delta], array_flip($columns));
|
||||
$values[$langcode][$delta] = $item_columns_to_sync + $item_columns_to_keep;
|
||||
$values[$langcode][$delta] = $this->createMergedItem($source_items[$delta], $original_field_values[$langcode][$delta], $properties);
|
||||
}
|
||||
// If the delta doesn't exist, copy from the source language.
|
||||
elseif ($created) {
|
||||
|
@ -190,13 +286,37 @@ class FieldTranslationSynchronizer implements FieldTranslationSynchronizerInterf
|
|||
// If the value has only been reordered we just move the old one in
|
||||
// the new position.
|
||||
$item = isset($original_field_values[$langcode][$old_delta]) ? $original_field_values[$langcode][$old_delta] : $source_items[$new_delta];
|
||||
$values[$langcode][$new_delta] = $item;
|
||||
// When saving a default revision starting from a pending revision,
|
||||
// we may have desynchronized field values, so we make sure that
|
||||
// untranslatable properties are synchronized, even if in any other
|
||||
// situation this would not be necessary.
|
||||
$values[$langcode][$new_delta] = $this->createMergedItem($source_items[$new_delta], $item, $properties);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a merged item.
|
||||
*
|
||||
* @param array $source_item
|
||||
* An item containing the untranslatable properties to be synchronized.
|
||||
* @param array $target_item
|
||||
* An item containing the translatable properties to be kept.
|
||||
* @param string[] $properties
|
||||
* An array of properties to be synchronized.
|
||||
*
|
||||
* @return array
|
||||
* A merged item array.
|
||||
*/
|
||||
protected function createMergedItem(array $source_item, array $target_item, array $properties) {
|
||||
$property_keys = array_flip($properties);
|
||||
$item_properties_to_sync = array_intersect_key($source_item, $property_keys);
|
||||
$item_properties_to_keep = array_diff_key($target_item, $property_keys);
|
||||
return $item_properties_to_sync + $item_properties_to_keep;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a hash code for the specified item.
|
||||
*
|
||||
|
@ -204,19 +324,19 @@ class FieldTranslationSynchronizer implements FieldTranslationSynchronizerInterf
|
|||
* An array of field items.
|
||||
* @param int $delta
|
||||
* The delta identifying the item to be processed.
|
||||
* @param array $columns
|
||||
* @param array $properties
|
||||
* An array of column names to be synchronized.
|
||||
*
|
||||
* @returns string
|
||||
* A hash code that can be used to identify the item.
|
||||
*/
|
||||
protected function itemHash(array $items, $delta, array $columns) {
|
||||
protected function itemHash(array $items, $delta, array $properties) {
|
||||
$values = [];
|
||||
|
||||
if (isset($items[$delta])) {
|
||||
foreach ($columns as $column) {
|
||||
if (!empty($items[$delta][$column])) {
|
||||
$value = $items[$delta][$column];
|
||||
foreach ($properties as $property) {
|
||||
if (!empty($items[$delta][$property])) {
|
||||
$value = $items[$delta][$property];
|
||||
// String and integer values are by far the most common item values,
|
||||
// thus we special-case them to improve performance.
|
||||
$values[] = is_string($value) || is_int($value) ? $value : hash('sha256', serialize($value));
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace Drupal\content_translation;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
|
||||
/**
|
||||
* Provides field translation synchronization capabilities.
|
||||
|
@ -49,9 +50,20 @@ interface FieldTranslationSynchronizerInterface {
|
|||
* The language code of the items to use as source values.
|
||||
* @param array $translations
|
||||
* An array of all the available language codes for the given field.
|
||||
* @param array $columns
|
||||
* An array of column names to be synchronized.
|
||||
* @param array $properties
|
||||
* An array of property names to be synchronized.
|
||||
*/
|
||||
public function synchronizeItems(array &$field_values, array $unchanged_items, $sync_langcode, array $translations, array $columns);
|
||||
public function synchronizeItems(array &$field_values, array $unchanged_items, $sync_langcode, array $translations, array $properties);
|
||||
|
||||
/**
|
||||
* Returns the synchronized properties for the specified field definition.
|
||||
*
|
||||
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
|
||||
* A field definition.
|
||||
*
|
||||
* @return string[]
|
||||
* An array of synchronized field property names.
|
||||
*/
|
||||
public function getFieldSynchronizedProperties(FieldDefinitionInterface $field_definition);
|
||||
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ use Drupal\Core\Language\LanguageInterface;
|
|||
|
||||
/**
|
||||
* Delete translation form for content_translation module.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ContentTranslationDeleteForm extends ContentEntityDeleteForm {
|
||||
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_translation\Plugin\Validation\Constraint;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
/**
|
||||
* Validation constraint for the entity changed timestamp.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @Constraint(
|
||||
* id = "ContentTranslationSynchronizedFields",
|
||||
* label = @Translation("Content translation synchronized fields", context = "Validation"),
|
||||
* type = {"entity"}
|
||||
* )
|
||||
*/
|
||||
class ContentTranslationSynchronizedFieldsConstraint extends Constraint {
|
||||
|
||||
// In this case "elements" refers to "field properties", in fact it is what we
|
||||
// are using in the UI elsewhere.
|
||||
public $defaultRevisionMessage = 'Non-translatable field elements can only be changed when updating the current revision.';
|
||||
public $defaultTranslationMessage = 'Non-translatable field elements can only be changed when updating the original language.';
|
||||
|
||||
}
|
|
@ -0,0 +1,226 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_translation\Plugin\Validation\Constraint;
|
||||
|
||||
use Drupal\content_translation\ContentTranslationManagerInterface;
|
||||
use Drupal\content_translation\FieldTranslationSynchronizerInterface;
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
|
||||
/**
|
||||
* Checks that synchronized fields are handled correctly in pending revisions.
|
||||
*
|
||||
* As for untranslatable fields, two modes are supported:
|
||||
* - When changes to untranslatable fields are configured to affect all revision
|
||||
* translations, synchronized field properties can be changed only in default
|
||||
* revisions.
|
||||
* - When changes to untranslatable fields affect are configured to affect only
|
||||
* the revision's default translation, 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. These are
|
||||
* actually synchronized when saving changes to the default translation as a
|
||||
* new default revision.
|
||||
*
|
||||
* @see \Drupal\content_translation\Plugin\Validation\Constraint\ContentTranslationSynchronizedFieldsConstraint
|
||||
* @see \Drupal\Core\Entity\Plugin\Validation\Constraint\EntityUntranslatableFieldsConstraintValidator
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ContentTranslationSynchronizedFieldsConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The content translation manager.
|
||||
*
|
||||
* @var \Drupal\content_translation\ContentTranslationManagerInterface
|
||||
*/
|
||||
protected $contentTranslationManager;
|
||||
|
||||
/**
|
||||
* The field translation synchronizer.
|
||||
*
|
||||
* @var \Drupal\content_translation\FieldTranslationSynchronizerInterface
|
||||
*/
|
||||
protected $synchronizer;
|
||||
|
||||
/**
|
||||
* ContentTranslationSynchronizedFieldsConstraintValidator constructor.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\content_translation\ContentTranslationManagerInterface $content_translation_manager
|
||||
* The content translation manager.
|
||||
* @param \Drupal\content_translation\FieldTranslationSynchronizerInterface $synchronizer
|
||||
* The field translation synchronizer.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager, ContentTranslationManagerInterface $content_translation_manager, FieldTranslationSynchronizerInterface $synchronizer) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->contentTranslationManager = $content_translation_manager;
|
||||
$this->synchronizer = $synchronizer;
|
||||
}
|
||||
|
||||
/**
|
||||
* [@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('content_translation.manager'),
|
||||
$container->get('content_translation.synchronizer')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validate($value, Constraint $constraint) {
|
||||
/** @var \Drupal\content_translation\Plugin\Validation\Constraint\ContentTranslationSynchronizedFieldsConstraint $constraint */
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
$entity = $value;
|
||||
if ($entity->isNew() || !$entity->getEntityType()->isRevisionable()) {
|
||||
return;
|
||||
}
|
||||
// When changes to untranslatable fields are configured to affect all
|
||||
// revision translations, we always allow changes in default revisions.
|
||||
if ($entity->isDefaultRevision() && !$entity->isDefaultTranslationAffectedOnly()) {
|
||||
return;
|
||||
}
|
||||
$entity_type_id = $entity->getEntityTypeId();
|
||||
if (!$this->contentTranslationManager->isEnabled($entity_type_id, $entity->bundle())) {
|
||||
return;
|
||||
}
|
||||
$synchronized_properties = $this->getSynchronizedPropertiesByField($entity->getFieldDefinitions());
|
||||
if (!$synchronized_properties) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $original */
|
||||
$original = $this->getOriginalEntity($entity);
|
||||
$original_translation = $this->getOriginalTranslation($entity, $original);
|
||||
if ($this->hasSynchronizedPropertyChanges($entity, $original_translation, $synchronized_properties)) {
|
||||
if ($entity->isDefaultTranslationAffectedOnly()) {
|
||||
foreach ($entity->getTranslationLanguages(FALSE) as $langcode => $language) {
|
||||
if ($entity->getTranslation($langcode)->hasTranslationChanges()) {
|
||||
$this->context->addViolation($constraint->defaultTranslationMessage);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->context->addViolation($constraint->defaultRevisionMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether any synchronized property has changes.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
|
||||
* The entity being validated.
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $original
|
||||
* The original unchanged entity.
|
||||
* @param string[][] $synchronized_properties
|
||||
* An associative array of arrays of synchronized field properties keyed by
|
||||
* field name.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if changes in synchronized properties were detected, FALSE
|
||||
* otherwise.
|
||||
*/
|
||||
protected function hasSynchronizedPropertyChanges(ContentEntityInterface $entity, ContentEntityInterface $original, array $synchronized_properties) {
|
||||
foreach ($synchronized_properties as $field_name => $properties) {
|
||||
foreach ($properties as $property) {
|
||||
$items = $entity->get($field_name)->getValue();
|
||||
$original_items = $original->get($field_name)->getValue();
|
||||
if (count($items) !== count($original_items)) {
|
||||
return TRUE;
|
||||
}
|
||||
foreach ($items as $delta => $item) {
|
||||
// @todo This loose comparison is not fully reliable. Revisit this
|
||||
// after https://www.drupal.org/project/drupal/issues/2941092.
|
||||
if ($items[$delta][$property] != $original_items[$delta][$property]) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the original unchanged entity to be used to detect changes.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
|
||||
* The entity being changed.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\ContentEntityInterface
|
||||
* The unchanged entity.
|
||||
*/
|
||||
protected function getOriginalEntity(ContentEntityInterface $entity) {
|
||||
if (!isset($entity->original)) {
|
||||
$storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
|
||||
$original = $entity->isDefaultRevision() ? $storage->loadUnchanged($entity->id()) : $storage->loadRevision($entity->getLoadedRevisionId());
|
||||
}
|
||||
else {
|
||||
$original = $entity->original;
|
||||
}
|
||||
return $original;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the original translation.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
|
||||
* The entity being validated.
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $original
|
||||
* The original entity.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\ContentEntityInterface
|
||||
* The original entity translation object.
|
||||
*/
|
||||
protected function getOriginalTranslation(ContentEntityInterface $entity, ContentEntityInterface $original) {
|
||||
$langcode = $entity->language()->getId();
|
||||
if ($original->hasTranslation($langcode)) {
|
||||
$original_langcode = $langcode;
|
||||
}
|
||||
else {
|
||||
$metadata = $this->contentTranslationManager->getTranslationMetadata($entity);
|
||||
$original_langcode = $metadata->getSource();
|
||||
}
|
||||
return $original->getTranslation($original_langcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the synchronized properties for every specified field.
|
||||
*
|
||||
* @param \Drupal\Core\Field\FieldDefinitionInterface[] $field_definitions
|
||||
* An array of field definitions.
|
||||
*
|
||||
* @return string[][]
|
||||
* An associative array of arrays of field property names keyed by field
|
||||
* name.
|
||||
*/
|
||||
public function getSynchronizedPropertiesByField(array $field_definitions) {
|
||||
$synchronizer = $this->synchronizer;
|
||||
$synchronized_properties = array_filter(array_map(
|
||||
function (FieldDefinitionInterface $field_definition) use ($synchronizer) {
|
||||
return $synchronizer->getFieldSynchronizedProperties($field_definition);
|
||||
},
|
||||
$field_definitions
|
||||
));
|
||||
return $synchronized_properties;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_translation\Plugin\migrate\source;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Gets an i18n translation from the source database.
|
||||
*/
|
||||
trait I18nQueryTrait {
|
||||
|
||||
/**
|
||||
* The i18n string table name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $i18nStringTable;
|
||||
|
||||
/**
|
||||
* Gets the translation for the property not already in the row.
|
||||
*
|
||||
* For some i18n migrations there are two translation values, such as a
|
||||
* translated title and a translated description, that need to be retrieved.
|
||||
* Since these values are stored in separate rows of the i18nStringTable
|
||||
* table we get them individually, one in the source plugin query() and the
|
||||
* other in prepareRow(). The names of the properties varies, for example,
|
||||
* in BoxTranslation they are 'body' and 'title' whereas in
|
||||
* MenuLinkTranslation they are 'title' and 'description'. This will save both
|
||||
* translations to the row.
|
||||
*
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The current migration row which must include both a 'language' property
|
||||
* and an 'objectid' property. The 'objectid' is the value for the
|
||||
* 'objectid' field in the i18n_string table.
|
||||
* @param string $property_not_in_row
|
||||
* The name of the property to get the translation for.
|
||||
* @param string $object_id_name
|
||||
* The value of the objectid in the i18n table.
|
||||
* @param \Drupal\migrate\Plugin\MigrateIdMapInterface $id_map
|
||||
* The ID map.
|
||||
*
|
||||
* @return bool
|
||||
* FALSE if the property has already been migrated.
|
||||
*
|
||||
* @throws \Drupal\migrate\MigrateException
|
||||
*/
|
||||
protected function getPropertyNotInRowTranslation(Row $row, $property_not_in_row, $object_id_name, MigrateIdMapInterface $id_map) {
|
||||
$language = $row->getSourceProperty('language');
|
||||
if (!$language) {
|
||||
throw new MigrateException('No language found.');
|
||||
}
|
||||
$object_id = $row->getSourceProperty($object_id_name);
|
||||
if (!$object_id) {
|
||||
throw new MigrateException('No objectid found.');
|
||||
}
|
||||
|
||||
// If this row has been migrated it is a duplicate so skip it.
|
||||
if ($id_map->lookupDestinationIds([$object_id_name => $object_id, 'language' => $language])) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Save the translation for the property already in the row.
|
||||
$property_in_row = $row->getSourceProperty('property');
|
||||
$row->setSourceProperty($property_in_row . '_translated', $row->getSourceProperty('translation'));
|
||||
|
||||
// Get the translation, if one exists, for the property not already in the
|
||||
// row.
|
||||
$query = $this->select($this->i18nStringTable, 'i18n')
|
||||
->fields('i18n', ['lid'])
|
||||
->condition('i18n.property', $property_not_in_row)
|
||||
->condition('i18n.objectid', $object_id);
|
||||
$query->leftJoin('locales_target', 'lt', 'i18n.lid = lt.lid');
|
||||
$query->condition('lt.language', $language);
|
||||
$query->addField('lt', 'translation');
|
||||
$results = $query->execute()->fetchAssoc();
|
||||
if (!$results) {
|
||||
$row->setSourceProperty($property_not_in_row . '_translated', NULL);
|
||||
}
|
||||
else {
|
||||
$row->setSourceProperty($property_not_in_row . '_translated', $results['translation']);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_translation\Plugin\migrate\source\d7;
|
||||
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
|
||||
/**
|
||||
* Drupal 7 Entity Translation settings from variables.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d7_entity_translation_settings",
|
||||
* source_module = "entity_translation"
|
||||
* )
|
||||
*/
|
||||
class EntityTranslationSettings extends DrupalSqlBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
// Query all meaningful variables for entity translation.
|
||||
$query = $this->select('variable', 'v')
|
||||
->fields('v', ['name', 'value']);
|
||||
$condition = $query->orConditionGroup()
|
||||
// The 'entity_translation_entity_types' variable tells us which entity
|
||||
// type uses entity translation.
|
||||
->condition('name', 'entity_translation_entity_types')
|
||||
// The 'entity_translation_taxonomy' variable tells us which taxonomy
|
||||
// vocabulary uses entity_translation.
|
||||
->condition('name', 'entity_translation_taxonomy')
|
||||
// The 'entity_translation_settings_%' variables give us the entity
|
||||
// translation settings for each entity type and each bundle.
|
||||
->condition('name', 'entity_translation_settings_%', 'LIKE')
|
||||
// The 'language_content_type_%' variables tells us which node type and
|
||||
// which comment type uses entity translation.
|
||||
->condition('name', 'language_content_type_%', 'LIKE');
|
||||
$query->condition($condition);
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function initializeIterator() {
|
||||
$results = array_map('unserialize', $this->prepareQuery()->execute()->fetchAllKeyed());
|
||||
$rows = [];
|
||||
|
||||
// Find out which entity type uses entity translation by looking at the
|
||||
// 'entity_translation_entity_types' variable.
|
||||
$entity_types = array_filter($results['entity_translation_entity_types']);
|
||||
|
||||
// If no entity type uses entity translation, there's nothing to do.
|
||||
if (empty($entity_types)) {
|
||||
return new \ArrayIterator($rows);
|
||||
}
|
||||
|
||||
// Find out which node type uses entity translation by looking at the
|
||||
// 'language_content_type_%' variables.
|
||||
$node_types = [];
|
||||
foreach ($results as $name => $value) {
|
||||
if (preg_match('/^language_content_type_(.+)$/', $name, $matches) && (int) $value === 4) {
|
||||
$node_types[] = $matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
// Find out which vocabulary uses entity translation by looking at the
|
||||
// 'entity_translation_taxonomy' variable.
|
||||
$vocabularies = [];
|
||||
if (isset($results['entity_translation_taxonomy']) && is_array($results['entity_translation_taxonomy'])) {
|
||||
$vocabularies = array_keys(array_filter($results['entity_translation_taxonomy']));
|
||||
}
|
||||
|
||||
if (in_array('node', $entity_types, TRUE) && !empty($node_types)) {
|
||||
// For each node type that uses entity translation, check if a
|
||||
// settings variable exists for that node type, otherwise use default
|
||||
// values.
|
||||
foreach ($node_types as $node_type) {
|
||||
$settings = isset($results['entity_translation_settings_node__' . $node_type]) ? $results['entity_translation_settings_node__' . $node_type] : [];
|
||||
$rows[] = [
|
||||
'id' => 'node.' . $node_type,
|
||||
'target_entity_type_id' => 'node',
|
||||
'target_bundle' => $node_type,
|
||||
'default_langcode' => isset($settings['default_language']) ? $settings['default_language'] : 'und',
|
||||
// The Drupal 7 'hide_language_selector' configuration has become
|
||||
// 'language_alterable' in Drupal 8 so we need to negate the value we
|
||||
// receive from the source. The Drupal 7 'hide_language_selector'
|
||||
// default value for the node entity type was FALSE so in Drupal 8 it
|
||||
// should be set to TRUE, unlike the other entity types for which
|
||||
// it's the opposite.
|
||||
'language_alterable' => isset($settings['hide_language_selector']) ? (bool) !$settings['hide_language_selector'] : TRUE,
|
||||
'untranslatable_fields_hide' => isset($settings['shared_fields_original_only']) ? (bool) $settings['shared_fields_original_only'] : FALSE,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (in_array('comment', $entity_types, TRUE) && !empty($node_types)) {
|
||||
// A comment type uses entity translation if the associated node type
|
||||
// uses it. So, for each node type that uses entity translation, check
|
||||
// if a settings variable exists for that comment type, otherwise use
|
||||
// default values.
|
||||
foreach ($node_types as $node_type) {
|
||||
$settings = isset($results['entity_translation_settings_comment__comment_node_' . $node_type]) ? $results['entity_translation_settings_comment__comment_node_' . $node_type] : [];
|
||||
// Forum uses a hardcoded comment type name, so make sure we use it
|
||||
// when we're dealing with forum comment type.
|
||||
$bundle = $node_type == 'forum' ? 'comment_forum' : 'comment_node_' . $node_type;
|
||||
$rows[] = [
|
||||
'id' => 'comment.' . $bundle,
|
||||
'target_entity_type_id' => 'comment',
|
||||
'target_bundle' => $bundle,
|
||||
'default_langcode' => isset($settings['default_language']) ? $settings['default_language'] : 'xx-et-current',
|
||||
// The Drupal 7 'hide_language_selector' configuration has become
|
||||
// 'language_alterable' in Drupal 8 so we need to negate the value we
|
||||
// receive from the source. The Drupal 7 'hide_language_selector'
|
||||
// default value for the comment entity type was TRUE so in Drupal 8
|
||||
// it should be set to FALSE.
|
||||
'language_alterable' => isset($settings['hide_language_selector']) ? (bool) !$settings['hide_language_selector'] : FALSE,
|
||||
'untranslatable_fields_hide' => isset($settings['shared_fields_original_only']) ? (bool) $settings['shared_fields_original_only'] : FALSE,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (in_array('taxonomy_term', $entity_types, TRUE) && !empty($vocabularies)) {
|
||||
// For each vocabulary that uses entity translation, check if a
|
||||
// settings variable exists for that vocabulary, otherwise use default
|
||||
// values.
|
||||
foreach ($vocabularies as $vocabulary) {
|
||||
$settings = isset($results['entity_translation_settings_taxonomy_term__' . $vocabulary]) ? $results['entity_translation_settings_taxonomy_term__' . $vocabulary] : [];
|
||||
$rows[] = [
|
||||
'id' => 'taxonomy_term.' . $vocabulary,
|
||||
'target_entity_type_id' => 'taxonomy_term',
|
||||
'target_bundle' => $vocabulary,
|
||||
'default_langcode' => isset($settings['default_language']) ? $settings['default_language'] : 'xx-et-default',
|
||||
// The Drupal 7 'hide_language_selector' configuration has become
|
||||
// 'language_alterable' in Drupal 8 so we need to negate the value we
|
||||
// receive from the source. The Drupal 7 'hide_language_selector'
|
||||
// default value for the taxonomy_term entity type was TRUE so in
|
||||
// Drupal 8 it should be set to FALSE.
|
||||
'language_alterable' => isset($settings['hide_language_selector']) ? (bool) !$settings['hide_language_selector'] : FALSE,
|
||||
'untranslatable_fields_hide' => isset($settings['shared_fields_original_only']) ? (bool) $settings['shared_fields_original_only'] : FALSE,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (in_array('user', $entity_types, TRUE)) {
|
||||
// User entity type is not bundleable. Check if a settings variable
|
||||
// exists, otherwise use default values.
|
||||
$settings = isset($results['entity_translation_settings_user__user']) ? $results['entity_translation_settings_user__user'] : [];
|
||||
$rows[] = [
|
||||
'id' => 'user.user',
|
||||
'target_entity_type_id' => 'user',
|
||||
'target_bundle' => 'user',
|
||||
'default_langcode' => isset($settings['default_language']) ? $settings['default_language'] : 'xx-et-default',
|
||||
// The Drupal 7 'hide_language_selector' configuration has become
|
||||
// 'language_alterable' in Drupal 8 so we need to negate the value we
|
||||
// receive from the source. The Drupal 7 'hide_language_selector'
|
||||
// default value for the user entity type was TRUE so in Drupal 8 it
|
||||
// should be set to FALSE.
|
||||
'language_alterable' => isset($settings['hide_language_selector']) ? (bool) !$settings['hide_language_selector'] : FALSE,
|
||||
'untranslatable_fields_hide' => isset($settings['shared_fields_original_only']) ? (bool) $settings['shared_fields_original_only'] : FALSE,
|
||||
];
|
||||
}
|
||||
|
||||
return new \ArrayIterator($rows);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return [
|
||||
'id' => $this->t('The configuration ID'),
|
||||
'target_entity_type_id' => $this->t('The target entity type ID'),
|
||||
'target_bundle' => $this->t('The target bundle'),
|
||||
'default_langcode' => $this->t('The default language'),
|
||||
'language_alterable' => $this->t('Whether to show language selector on create and edit pages'),
|
||||
'untranslatable_fields_hide' => $this->t('Whether to hide non translatable fields on translation forms'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['id']['type'] = 'string';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function count($refresh = FALSE) {
|
||||
// Since the number of variables we fetch with query() does not match the
|
||||
// actual number of rows generated by initializeIterator(), we need to
|
||||
// override count() to return the correct count.
|
||||
return (int) $this->initializeIterator()->count();
|
||||
}
|
||||
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Drupal\content_translation\Routing;
|
||||
|
||||
use Drupal\content_translation\ContentTranslationManager;
|
||||
use Drupal\content_translation\ContentTranslationManagerInterface;
|
||||
use Drupal\Core\Routing\RouteSubscriberBase;
|
||||
use Drupal\Core\Routing\RoutingEvents;
|
||||
|
@ -35,18 +36,6 @@ class ContentTranslationRouteSubscriber extends RouteSubscriberBase {
|
|||
*/
|
||||
protected function alterRoutes(RouteCollection $collection) {
|
||||
foreach ($this->contentTranslationManager->getSupportedEntityTypes() as $entity_type_id => $entity_type) {
|
||||
// Try to get the route from the current collection.
|
||||
$link_template = $entity_type->getLinkTemplate('canonical');
|
||||
if (strpos($link_template, '/') !== FALSE) {
|
||||
$base_path = '/' . $link_template;
|
||||
}
|
||||
else {
|
||||
if (!$entity_route = $collection->get("entity.$entity_type_id.canonical")) {
|
||||
continue;
|
||||
}
|
||||
$base_path = $entity_route->getPath();
|
||||
}
|
||||
|
||||
// Inherit admin route status from edit route, if exists.
|
||||
$is_admin = FALSE;
|
||||
$route_name = "entity.$entity_type_id.edit_form";
|
||||
|
@ -54,110 +43,130 @@ class ContentTranslationRouteSubscriber extends RouteSubscriberBase {
|
|||
$is_admin = (bool) $edit_route->getOption('_admin_route');
|
||||
}
|
||||
|
||||
$path = $base_path . '/translations';
|
||||
$load_latest_revision = ContentTranslationManager::isPendingRevisionSupportEnabled($entity_type_id);
|
||||
|
||||
$route = new Route(
|
||||
$path,
|
||||
[
|
||||
'_controller' => '\Drupal\content_translation\Controller\ContentTranslationController::overview',
|
||||
'entity_type_id' => $entity_type_id,
|
||||
],
|
||||
[
|
||||
'_entity_access' => $entity_type_id . '.view',
|
||||
'_access_content_translation_overview' => $entity_type_id,
|
||||
],
|
||||
[
|
||||
'parameters' => [
|
||||
$entity_type_id => [
|
||||
'type' => 'entity:' . $entity_type_id,
|
||||
],
|
||||
if ($entity_type->hasLinkTemplate('drupal:content-translation-overview')) {
|
||||
$route = new Route(
|
||||
$entity_type->getLinkTemplate('drupal:content-translation-overview'),
|
||||
[
|
||||
'_controller' => '\Drupal\content_translation\Controller\ContentTranslationController::overview',
|
||||
'entity_type_id' => $entity_type_id,
|
||||
],
|
||||
'_admin_route' => $is_admin,
|
||||
]
|
||||
);
|
||||
$route_name = "entity.$entity_type_id.content_translation_overview";
|
||||
$collection->add($route_name, $route);
|
||||
|
||||
$route = new Route(
|
||||
$path . '/add/{source}/{target}',
|
||||
[
|
||||
'_controller' => '\Drupal\content_translation\Controller\ContentTranslationController::add',
|
||||
'source' => NULL,
|
||||
'target' => NULL,
|
||||
'_title' => 'Add',
|
||||
'entity_type_id' => $entity_type_id,
|
||||
|
||||
],
|
||||
[
|
||||
'_entity_access' => $entity_type_id . '.view',
|
||||
'_access_content_translation_manage' => 'create',
|
||||
],
|
||||
[
|
||||
'parameters' => [
|
||||
'source' => [
|
||||
'type' => 'language',
|
||||
],
|
||||
'target' => [
|
||||
'type' => 'language',
|
||||
],
|
||||
$entity_type_id => [
|
||||
'type' => 'entity:' . $entity_type_id,
|
||||
],
|
||||
[
|
||||
'_entity_access' => $entity_type_id . '.view',
|
||||
'_access_content_translation_overview' => $entity_type_id,
|
||||
],
|
||||
'_admin_route' => $is_admin,
|
||||
]
|
||||
);
|
||||
$collection->add("entity.$entity_type_id.content_translation_add", $route);
|
||||
[
|
||||
'parameters' => [
|
||||
$entity_type_id => [
|
||||
'type' => 'entity:' . $entity_type_id,
|
||||
'load_latest_revision' => $load_latest_revision,
|
||||
],
|
||||
],
|
||||
'_admin_route' => $is_admin,
|
||||
]
|
||||
);
|
||||
$route_name = "entity.$entity_type_id.content_translation_overview";
|
||||
$collection->add($route_name, $route);
|
||||
}
|
||||
|
||||
$route = new Route(
|
||||
$path . '/edit/{language}',
|
||||
[
|
||||
'_controller' => '\Drupal\content_translation\Controller\ContentTranslationController::edit',
|
||||
'language' => NULL,
|
||||
'_title' => 'Edit',
|
||||
'entity_type_id' => $entity_type_id,
|
||||
],
|
||||
[
|
||||
'_access_content_translation_manage' => 'update',
|
||||
],
|
||||
[
|
||||
'parameters' => [
|
||||
'language' => [
|
||||
'type' => 'language',
|
||||
],
|
||||
$entity_type_id => [
|
||||
'type' => 'entity:' . $entity_type_id,
|
||||
],
|
||||
],
|
||||
'_admin_route' => $is_admin,
|
||||
]
|
||||
);
|
||||
$collection->add("entity.$entity_type_id.content_translation_edit", $route);
|
||||
if ($entity_type->hasLinkTemplate('drupal:content-translation-add')) {
|
||||
$route = new Route(
|
||||
$entity_type->getLinkTemplate('drupal:content-translation-add'),
|
||||
[
|
||||
'_controller' => '\Drupal\content_translation\Controller\ContentTranslationController::add',
|
||||
'source' => NULL,
|
||||
'target' => NULL,
|
||||
'_title' => 'Add',
|
||||
'entity_type_id' => $entity_type_id,
|
||||
|
||||
$route = new Route(
|
||||
$path . '/delete/{language}',
|
||||
[
|
||||
'_entity_form' => $entity_type_id . '.content_translation_deletion',
|
||||
'language' => NULL,
|
||||
'_title' => 'Delete',
|
||||
'entity_type_id' => $entity_type_id,
|
||||
],
|
||||
[
|
||||
'_access_content_translation_manage' => 'delete',
|
||||
],
|
||||
[
|
||||
'parameters' => [
|
||||
'language' => [
|
||||
'type' => 'language',
|
||||
],
|
||||
$entity_type_id => [
|
||||
'type' => 'entity:' . $entity_type_id,
|
||||
],
|
||||
],
|
||||
'_admin_route' => $is_admin,
|
||||
]
|
||||
);
|
||||
$collection->add("entity.$entity_type_id.content_translation_delete", $route);
|
||||
[
|
||||
'_entity_access' => $entity_type_id . '.view',
|
||||
'_access_content_translation_manage' => 'create',
|
||||
],
|
||||
[
|
||||
'parameters' => [
|
||||
'source' => [
|
||||
'type' => 'language',
|
||||
],
|
||||
'target' => [
|
||||
'type' => 'language',
|
||||
],
|
||||
$entity_type_id => [
|
||||
'type' => 'entity:' . $entity_type_id,
|
||||
'load_latest_revision' => $load_latest_revision,
|
||||
],
|
||||
],
|
||||
'_admin_route' => $is_admin,
|
||||
]
|
||||
);
|
||||
$collection->add("entity.$entity_type_id.content_translation_add", $route);
|
||||
}
|
||||
|
||||
if ($entity_type->hasLinkTemplate('drupal:content-translation-edit')) {
|
||||
$route = new Route(
|
||||
$entity_type->getLinkTemplate('drupal:content-translation-edit'),
|
||||
[
|
||||
'_controller' => '\Drupal\content_translation\Controller\ContentTranslationController::edit',
|
||||
'language' => NULL,
|
||||
'_title' => 'Edit',
|
||||
'entity_type_id' => $entity_type_id,
|
||||
],
|
||||
[
|
||||
'_access_content_translation_manage' => 'update',
|
||||
],
|
||||
[
|
||||
'parameters' => [
|
||||
'language' => [
|
||||
'type' => 'language',
|
||||
],
|
||||
$entity_type_id => [
|
||||
'type' => 'entity:' . $entity_type_id,
|
||||
'load_latest_revision' => $load_latest_revision,
|
||||
],
|
||||
],
|
||||
'_admin_route' => $is_admin,
|
||||
]
|
||||
);
|
||||
$collection->add("entity.$entity_type_id.content_translation_edit", $route);
|
||||
}
|
||||
|
||||
if ($entity_type->hasLinkTemplate('drupal:content-translation-delete')) {
|
||||
$route = new Route(
|
||||
$entity_type->getLinkTemplate('drupal:content-translation-delete'),
|
||||
[
|
||||
'_entity_form' => $entity_type_id . '.content_translation_deletion',
|
||||
'language' => NULL,
|
||||
'_title' => 'Delete',
|
||||
'entity_type_id' => $entity_type_id,
|
||||
],
|
||||
[
|
||||
'_access_content_translation_manage' => 'delete',
|
||||
],
|
||||
[
|
||||
'parameters' => [
|
||||
'language' => [
|
||||
'type' => 'language',
|
||||
],
|
||||
$entity_type_id => [
|
||||
'type' => 'entity:' . $entity_type_id,
|
||||
'load_latest_revision' => $load_latest_revision,
|
||||
],
|
||||
],
|
||||
'_admin_route' => $is_admin,
|
||||
]
|
||||
);
|
||||
$collection->add("entity.$entity_type_id.content_translation_delete", $route);
|
||||
}
|
||||
|
||||
// Add our custom translation deletion access checker.
|
||||
if ($load_latest_revision) {
|
||||
$entity_delete_route = $collection->get("entity.$entity_type_id.delete_form");
|
||||
if ($entity_delete_route) {
|
||||
$entity_delete_route->addRequirements(['_access_content_translation_delete' => "$entity_type_id.delete"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ use Drupal\Core\Language\Language;
|
|||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
|
||||
|
||||
/**
|
||||
|
@ -37,8 +37,7 @@ abstract class ContentTranslationUITestBase extends ContentTranslationTestBase {
|
|||
protected $testLanguageSelector = TRUE;
|
||||
|
||||
/**
|
||||
* Flag that tells whether the HTML escaping of all languages works or not
|
||||
* after SafeMarkup change.
|
||||
* Flag to determine if "all languages" rendering is tested.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
|
@ -113,12 +112,11 @@ abstract class ContentTranslationUITestBase extends ContentTranslationTestBase {
|
|||
$add_url = Url::fromRoute("entity.$entity_type_id.content_translation_add", [
|
||||
$entity->getEntityTypeId() => $entity->id(),
|
||||
'source' => $default_langcode,
|
||||
'target' => $langcode
|
||||
'target' => $langcode,
|
||||
], ['language' => $language]);
|
||||
$this->drupalPostForm($add_url, $this->getEditValues($values, $langcode), $this->getFormSubmitActionForNewTranslation($entity, $langcode));
|
||||
|
||||
// Assert that HTML is escaped in "all languages" in UI after SafeMarkup
|
||||
// change.
|
||||
// Assert that HTML is not escaped unexpectedly.
|
||||
if ($this->testHTMLEscapeForAllLanguages) {
|
||||
$this->assertNoRaw('<span class="translation-entity-all-languages">(all languages)</span>');
|
||||
$this->assertRaw('<span class="translation-entity-all-languages">(all languages)</span>');
|
||||
|
@ -141,10 +139,10 @@ abstract class ContentTranslationUITestBase extends ContentTranslationTestBase {
|
|||
$author_field_name = $entity->hasField('content_translation_uid') ? 'content_translation_uid' : 'uid';
|
||||
if ($entity->getFieldDefinition($author_field_name)->isTranslatable()) {
|
||||
$this->assertEqual($metadata_target_translation->getAuthor()->id(), $this->translator->id(),
|
||||
SafeMarkup::format('Author of the target translation @langcode correctly stored for translatable owner field.', ['@langcode' => $langcode]));
|
||||
new FormattableMarkup('Author of the target translation @langcode correctly stored for translatable owner field.', ['@langcode' => $langcode]));
|
||||
|
||||
$this->assertNotEqual($metadata_target_translation->getAuthor()->id(), $metadata_source_translation->getAuthor()->id(),
|
||||
SafeMarkup::format('Author of the target translation @target different from the author of the source translation @source for translatable owner field.',
|
||||
new FormattableMarkup('Author of the target translation @target different from the author of the source translation @source for translatable owner field.',
|
||||
['@target' => $langcode, '@source' => $default_langcode]));
|
||||
}
|
||||
else {
|
||||
|
@ -154,7 +152,7 @@ abstract class ContentTranslationUITestBase extends ContentTranslationTestBase {
|
|||
$created_field_name = $entity->hasField('content_translation_created') ? 'content_translation_created' : 'created';
|
||||
if ($entity->getFieldDefinition($created_field_name)->isTranslatable()) {
|
||||
$this->assertTrue($metadata_target_translation->getCreatedTime() > $metadata_source_translation->getCreatedTime(),
|
||||
SafeMarkup::format('Translation creation timestamp of the target translation @target is newer than the creation timestamp of the source translation @source for translatable created field.',
|
||||
new FormattableMarkup('Translation creation timestamp of the target translation @target is newer than the creation timestamp of the source translation @source for translatable created field.',
|
||||
['@target' => $langcode, '@source' => $default_langcode]));
|
||||
}
|
||||
else {
|
||||
|
@ -178,7 +176,7 @@ abstract class ContentTranslationUITestBase extends ContentTranslationTestBase {
|
|||
$add_url = Url::fromRoute("entity.$entity_type_id.content_translation_add", [
|
||||
$entity->getEntityTypeId() => $entity->id(),
|
||||
'source' => $default_langcode,
|
||||
'target' => $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.
|
||||
|
@ -192,7 +190,7 @@ abstract class ContentTranslationUITestBase extends ContentTranslationTestBase {
|
|||
$add_url = Url::fromRoute("entity.$entity_type_id.content_translation_add", [
|
||||
$entity->getEntityTypeId() => $entity->id(),
|
||||
'source' => $source_langcode,
|
||||
'target' => $langcode
|
||||
'target' => $langcode,
|
||||
], ['language' => $language]);
|
||||
$this->drupalPostForm($add_url, $edit, $this->getFormSubmitActionForNewTranslation($entity, $langcode));
|
||||
$storage->resetCache([$this->entityId]);
|
||||
|
|
|
@ -5,6 +5,6 @@ package: Testing
|
|||
version: VERSION
|
||||
core: 8.x
|
||||
dependencies:
|
||||
- content_translation
|
||||
- language
|
||||
- entity_test
|
||||
- drupal:content_translation
|
||||
- drupal:language
|
||||
- drupal:entity_test
|
||||
|
|
|
@ -5,7 +5,39 @@
|
|||
* Helper module for the Content Translation tests.
|
||||
*/
|
||||
|
||||
use \Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_entity_bundle_info_alter().
|
||||
*/
|
||||
function content_translation_test_entity_bundle_info_alter(&$bundles) {
|
||||
// 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().
|
||||
*/
|
||||
function content_translation_test_entity_access(EntityInterface $entity, $operation, AccountInterface $account) {
|
||||
$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().
|
||||
|
|
|
@ -5,5 +5,5 @@ package: Testing
|
|||
version: VERSION
|
||||
core: 8.x
|
||||
dependencies:
|
||||
- content_translation
|
||||
- views
|
||||
- drupal:content_translation
|
||||
- drupal:views
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_translation\Tests;
|
||||
namespace Drupal\Tests\content_translation\Functional;
|
||||
|
||||
/**
|
||||
* Tests the test content translation UI with the test entity.
|
|
@ -1,19 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_translation\Tests;
|
||||
namespace Drupal\Tests\content_translation\Functional;
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests that contextual links are available for content translation.
|
||||
*
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationContextualLinksTest extends WebTestBase {
|
||||
class ContentTranslationContextualLinksTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* The bundle being tested.
|
||||
|
@ -118,60 +117,11 @@ class ContentTranslationContextualLinksTest extends WebTestBase {
|
|||
$this->drupalPostForm('admin/config/regional/content-language', $edit, t('Save configuration'));
|
||||
$this->drupalLogout();
|
||||
|
||||
// Check that the translate link appears on the node page.
|
||||
// Check that the link leads to the translate page.
|
||||
$this->drupalLogin($this->translator);
|
||||
$translate_link = 'node/' . $node->id() . '/translations';
|
||||
|
||||
$response = $this->renderContextualLinks(['node:node=1:'], 'node/' . $node->id());
|
||||
$this->assertResponse(200);
|
||||
$json = Json::decode($response);
|
||||
$this->setRawContent($json['node:node=1:']);
|
||||
$this->assertLinkByHref($translate_link, 0, 'The contextual link to translate the node is shown.');
|
||||
|
||||
// Check that the link leads to the translate page.
|
||||
$this->drupalGet($translate_link);
|
||||
$this->assertRaw(t('Translations of %label', ['%label' => $node->label()]), 'The contextual link leads to the translate page.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get server-rendered contextual links for the given contextual link ids.
|
||||
*
|
||||
* Copied from \Drupal\contextual\Tests\ContextualDynamicContextTest::renderContextualLinks().
|
||||
*
|
||||
* @param array $ids
|
||||
* An array of contextual link ids.
|
||||
* @param string $current_path
|
||||
* The Drupal path for the page for which the contextual links are rendered.
|
||||
*
|
||||
* @return string
|
||||
* The response body.
|
||||
*/
|
||||
protected function renderContextualLinks($ids, $current_path) {
|
||||
// Build POST values.
|
||||
$post = [];
|
||||
for ($i = 0; $i < count($ids); $i++) {
|
||||
$post['ids[' . $i . ']'] = $ids[$i];
|
||||
}
|
||||
|
||||
// Serialize POST values.
|
||||
foreach ($post as $key => $value) {
|
||||
// Encode according to application/x-www-form-urlencoded
|
||||
// Both names and values needs to be urlencoded, according to
|
||||
// http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1
|
||||
$post[$key] = urlencode($key) . '=' . urlencode($value);
|
||||
}
|
||||
$post = implode('&', $post);
|
||||
|
||||
// Perform HTTP request.
|
||||
return $this->curlExec([
|
||||
CURLOPT_URL => \Drupal::url('contextual.render', [], ['absolute' => TRUE, 'query' => ['destination' => $current_path]]),
|
||||
CURLOPT_POST => TRUE,
|
||||
CURLOPT_POSTFIELDS => $post,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Accept: application/json',
|
||||
'Content-Type: application/x-www-form-urlencoded',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_translation\Tests;
|
||||
namespace Drupal\Tests\content_translation\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_translation\Tests;
|
||||
namespace Drupal\Tests\content_translation\Functional;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Test enabling content translation module.
|
||||
*
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationEnableTest extends WebTestBase {
|
||||
class ContentTranslationEnableTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
@ -36,7 +36,7 @@ class ContentTranslationEnableTest extends WebTestBase {
|
|||
// No pending updates should be available.
|
||||
$this->drupalGet('admin/reports/status');
|
||||
$requirement_value = $this->cssSelect("details.system-status-report__entry summary:contains('Entity/field definitions') + div");
|
||||
$this->assertEqual(t('Up to date'), trim((string) $requirement_value[0]));
|
||||
$this->assertEqual(t('Up to date'), trim($requirement_value[0]->getText()));
|
||||
|
||||
$this->drupalGet('admin/config/regional/content-language');
|
||||
// The node entity type should not be an option because it has no bundles.
|
||||
|
@ -54,7 +54,7 @@ class ContentTranslationEnableTest extends WebTestBase {
|
|||
// No pending updates should be available.
|
||||
$this->drupalGet('admin/reports/status');
|
||||
$requirement_value = $this->cssSelect("details.system-status-report__entry summary:contains('Entity/field definitions') + div");
|
||||
$this->assertEqual(t('Up to date'), trim((string) $requirement_value[0]));
|
||||
$this->assertEqual(t('Up to date'), trim($requirement_value[0]->getText()));
|
||||
|
||||
// Create a node type and check the content translation settings are now
|
||||
// available for nodes.
|
|
@ -1,9 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_translation\Tests;
|
||||
namespace Drupal\Tests\content_translation\Functional;
|
||||
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\node\Tests\NodeTestBase;
|
||||
use Drupal\Tests\node\Functional\NodeTestBase;
|
||||
use Drupal\Tests\TestFileCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests the content translation language that is set.
|
||||
|
@ -12,6 +13,10 @@ use Drupal\node\Tests\NodeTestBase;
|
|||
*/
|
||||
class ContentTranslationLanguageChangeTest extends NodeTestBase {
|
||||
|
||||
use TestFileCreationTrait {
|
||||
getTestFiles as drupalGetTestFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
|
@ -73,12 +78,12 @@ class ContentTranslationLanguageChangeTest extends NodeTestBase {
|
|||
$edit = [
|
||||
'title[0][value]' => 'english_title',
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, t('Save and publish'));
|
||||
$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
|
||||
// Create a translation in French.
|
||||
$this->clickLink('Translate');
|
||||
$this->clickLink('Add');
|
||||
$this->drupalPostForm(NULL, [], t('Save and keep published (this translation)'));
|
||||
$this->drupalPostForm(NULL, [], t('Save (this translation)'));
|
||||
$this->clickLink('Translate');
|
||||
|
||||
// Edit English translation.
|
||||
|
@ -90,7 +95,7 @@ class ContentTranslationLanguageChangeTest extends NodeTestBase {
|
|||
'files[field_image_field_0]' => $images->uri,
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, t('Upload'));
|
||||
$this->drupalPostForm(NULL, ['field_image_field[0][alt]' => 'alternative_text'], t('Save and keep published (this translation)'));
|
||||
$this->drupalPostForm(NULL, ['field_image_field[0][alt]' => 'alternative_text'], t('Save (this translation)'));
|
||||
|
||||
// Check that the translation languages are correct.
|
||||
$node = $this->getNodeByTitle('english_title');
|
||||
|
@ -109,13 +114,13 @@ class ContentTranslationLanguageChangeTest extends NodeTestBase {
|
|||
'title[0][value]' => 'english_title',
|
||||
'test_field_only_en_fr' => 'node created',
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, t('Save and publish'));
|
||||
$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
$this->assertEqual('node created', \Drupal::state()->get('test_field_only_en_fr'));
|
||||
|
||||
// Create a translation in French.
|
||||
$this->clickLink('Translate');
|
||||
$this->clickLink('Add');
|
||||
$this->drupalPostForm(NULL, [], t('Save and keep published (this translation)'));
|
||||
$this->drupalPostForm(NULL, [], t('Save (this translation)'));
|
||||
$this->clickLink('Translate');
|
||||
|
||||
// Edit English translation.
|
||||
|
@ -135,9 +140,9 @@ class ContentTranslationLanguageChangeTest extends NodeTestBase {
|
|||
$this->assertRaw('<title>Edit Article english_title | Drupal</title>');
|
||||
$edit = [
|
||||
'langcode[0][value]' => 'en',
|
||||
'field_image_field[0][alt]' => 'alternative_text'
|
||||
'field_image_field[0][alt]' => 'alternative_text',
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, t('Save and keep published (this translation)'));
|
||||
$this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
|
||||
|
||||
// Check that the translation languages are correct.
|
||||
$node = $this->getNodeByTitle('english_title');
|
|
@ -98,14 +98,14 @@ class ContentTranslationOperationsTest extends NodeTestBase {
|
|||
'access content' => TRUE,
|
||||
]
|
||||
);
|
||||
$node->setPublished(FALSE)->save();
|
||||
$node->setUnpublished()->save();
|
||||
$this->drupalGet($node->urlInfo('drupal:content-translation-overview'));
|
||||
$this->assertResponse(403);
|
||||
$this->drupalLogout();
|
||||
|
||||
// Ensure the 'Translate' local task does not show up anymore when disabling
|
||||
// translations for a content type.
|
||||
$node->setPublished(TRUE)->save();
|
||||
$node->setPublished()->save();
|
||||
user_role_change_permissions(
|
||||
Role::AUTHENTICATED_ID,
|
||||
[
|
||||
|
@ -136,7 +136,7 @@ class ContentTranslationOperationsTest extends NodeTestBase {
|
|||
$this->assertFalse(content_translation_translate_access($node)->isAllowed());
|
||||
$access_control_handler->resetCache();
|
||||
|
||||
$node->setPublished(TRUE);
|
||||
$node->setPublished();
|
||||
$node->save();
|
||||
$this->assertTrue(content_translation_translate_access($node)->isAllowed());
|
||||
$access_control_handler->resetCache();
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
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 function setUp() {
|
||||
parent::setUp();
|
||||
$this->enableContentModeration();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that outdated revision translations work correctly.
|
||||
*/
|
||||
public function testFlagAsOutdatedHidden() {
|
||||
// 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->drupalPostForm(NULL, $edit, t('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->drupalPostForm(NULL, $edit, t('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.
|
||||
*/
|
||||
protected function assertFlagWidget() {
|
||||
$this->assertSession()->pageTextNotContains('Flag other translations as outdated');
|
||||
$this->assertSession()->pageTextContains('Translations cannot be flagged as outdated when content is moderated.');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
<?php
|
||||
|
||||
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}
|
||||
*/
|
||||
public 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() {
|
||||
$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() {
|
||||
$this->drupalLogin($this->rootUser);
|
||||
$workflow_id = 'editorial';
|
||||
$this->drupalGet('/admin/config/workflow/workflows');
|
||||
$edit['bundles[' . $this->bundle . ']'] = TRUE;
|
||||
$this->drupalPostForm('admin/config/workflow/workflows/manage/' . $workflow_id . '/type/' . $this->entityTypeId, $edit, t('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,214 @@
|
|||
<?php
|
||||
|
||||
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 function setUp() {
|
||||
parent::setUp();
|
||||
$this->enableContentModeration();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that translation overview handles pending revisions correctly.
|
||||
*/
|
||||
public function testOverview() {
|
||||
$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) {
|
||||
$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->drupalPostForm(NULL, $edit, t('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()->pageTextContains($warning);
|
||||
$this->drupalGet($this->getEditUrl($it_revision));
|
||||
$this->assertSession()->buttonNotExists('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->drupalPostForm(NULL, $edit, t('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()->pageTextNotContains($warning);
|
||||
$this->drupalGet($this->getEditUrl($it_revision));
|
||||
$this->assertSession()->buttonExists('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->drupalPostForm(NULL, $edit, t('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->drupalPostForm(NULL, [], '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->assertTrue($it_revision->getRevisionId() < $entity->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->drupalPostForm(NULL, $edit, t('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->drupalPostForm(NULL, $edit, t('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->drupalPostForm(NULL, $edit, t('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->drupalPostForm(NULL, [], '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($it_delete_url);
|
||||
$this->drupalPostForm(NULL, [], '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->assertTrue($it_revision->getRevisionId() < $entity->getRevisionId());
|
||||
$this->drupalGet($overview_url);
|
||||
$this->assertSession()->linkByHrefNotExists($this->getEditUrl($it_revision)->toString());
|
||||
$this->assertSession()->linkByHrefExists($add_translation_href);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_translation\Tests;
|
||||
namespace Drupal\Tests\content_translation\Functional;
|
||||
|
||||
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
|
||||
use Drupal\comment\Tests\CommentTestTrait;
|
||||
|
@ -8,14 +8,14 @@ use Drupal\Core\Field\Entity\BaseFieldOverride;
|
|||
use Drupal\Core\Language\Language;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\language\Entity\ContentLanguageSettings;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the content translation settings UI.
|
||||
*
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationSettingsTest extends WebTestBase {
|
||||
class ContentTranslationSettingsTest extends BrowserTestBase {
|
||||
|
||||
use CommentTestTrait;
|
||||
|
||||
|
@ -154,7 +154,7 @@ class ContentTranslationSettingsTest extends WebTestBase {
|
|||
'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
|
||||
'settings[node][article][fields][title]' => TRUE,
|
||||
];
|
||||
$this->assertSettings('node', NULL, TRUE, $edit);
|
||||
|
||||
|
@ -193,7 +193,7 @@ class ContentTranslationSettingsTest extends WebTestBase {
|
|||
$elements = $this->xpath('//select[@id="edit-settings-node-article-settings-language-langcode"]/option');
|
||||
// Compare values inside the option elements with expected values.
|
||||
for ($i = 0; $i < count($elements); $i++) {
|
||||
$this->assertEqual($elements[$i]->attributes()->{'value'}, $expected_elements[$i]);
|
||||
$this->assertEqual($elements[$i]->getValue(), $expected_elements[$i]);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_translation\Tests;
|
||||
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.
|
||||
|
@ -14,6 +15,10 @@ use Drupal\file\Entity\File;
|
|||
*/
|
||||
class ContentTranslationSyncImageTest extends ContentTranslationTestBase {
|
||||
|
||||
use TestFileCreationTrait {
|
||||
getTestFiles as drupalGetTestFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* The cardinality of the image field.
|
||||
*
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
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;
|
||||
|
@ -138,7 +139,7 @@ abstract class ContentTranslationTestBase extends BrowserTestBase {
|
|||
* Returns an array of permissions needed for the administrator.
|
||||
*/
|
||||
protected function getAdministratorPermissions() {
|
||||
return array_merge($this->getEditorPermissions(), $this->getTranslatorPermissions(), ['administer content translation']);
|
||||
return array_merge($this->getEditorPermissions(), $this->getTranslatorPermissions(), ['administer languages', 'administer content translation']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -236,4 +237,24 @@ abstract class ContentTranslationTestBase extends BrowserTestBase {
|
|||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ class ContentTranslationUISkipTest extends BrowserTestBase {
|
|||
$admin_user = $this->drupalCreateUser([
|
||||
'translate any entity',
|
||||
'administer content translation',
|
||||
'administer languages'
|
||||
'administer languages',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
// Visit the content translation.
|
||||
|
|
|
@ -10,8 +10,7 @@ use Drupal\Core\Language\Language;
|
|||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
|
||||
use Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait;
|
||||
|
||||
/**
|
||||
* Tests the Content Translation UI.
|
||||
|
@ -35,8 +34,7 @@ abstract class ContentTranslationUITestBase extends ContentTranslationTestBase {
|
|||
protected $testLanguageSelector = TRUE;
|
||||
|
||||
/**
|
||||
* Flag that tells whether the HTML escaping of all languages works or not
|
||||
* after SafeMarkup change.
|
||||
* Flag to determine if "all languages" rendering is tested.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
|
@ -111,12 +109,11 @@ abstract class ContentTranslationUITestBase extends ContentTranslationTestBase {
|
|||
$add_url = Url::fromRoute("entity.$entity_type_id.content_translation_add", [
|
||||
$entity->getEntityTypeId() => $entity->id(),
|
||||
'source' => $default_langcode,
|
||||
'target' => $langcode
|
||||
'target' => $langcode,
|
||||
], ['language' => $language]);
|
||||
$this->drupalPostForm($add_url, $this->getEditValues($values, $langcode), $this->getFormSubmitActionForNewTranslation($entity, $langcode));
|
||||
|
||||
// Assert that HTML is escaped in "all languages" in UI after SafeMarkup
|
||||
// change.
|
||||
// Assert that HTML is not escaped unexpectedly.
|
||||
if ($this->testHTMLEscapeForAllLanguages) {
|
||||
$this->assertNoRaw('<span class="translation-entity-all-languages">(all languages)</span>');
|
||||
$this->assertRaw('<span class="translation-entity-all-languages">(all languages)</span>');
|
||||
|
@ -139,10 +136,10 @@ abstract class ContentTranslationUITestBase extends ContentTranslationTestBase {
|
|||
$author_field_name = $entity->hasField('content_translation_uid') ? 'content_translation_uid' : 'uid';
|
||||
if ($entity->getFieldDefinition($author_field_name)->isTranslatable()) {
|
||||
$this->assertEqual($metadata_target_translation->getAuthor()->id(), $this->translator->id(),
|
||||
SafeMarkup::format('Author of the target translation @langcode correctly stored for translatable owner field.', ['@langcode' => $langcode]));
|
||||
new FormattableMarkup('Author of the target translation @langcode correctly stored for translatable owner field.', ['@langcode' => $langcode]));
|
||||
|
||||
$this->assertNotEqual($metadata_target_translation->getAuthor()->id(), $metadata_source_translation->getAuthor()->id(),
|
||||
SafeMarkup::format('Author of the target translation @target different from the author of the source translation @source for translatable owner field.',
|
||||
new FormattableMarkup('Author of the target translation @target different from the author of the source translation @source for translatable owner field.',
|
||||
['@target' => $langcode, '@source' => $default_langcode]));
|
||||
}
|
||||
else {
|
||||
|
@ -152,7 +149,7 @@ abstract class ContentTranslationUITestBase extends ContentTranslationTestBase {
|
|||
$created_field_name = $entity->hasField('content_translation_created') ? 'content_translation_created' : 'created';
|
||||
if ($entity->getFieldDefinition($created_field_name)->isTranslatable()) {
|
||||
$this->assertTrue($metadata_target_translation->getCreatedTime() > $metadata_source_translation->getCreatedTime(),
|
||||
SafeMarkup::format('Translation creation timestamp of the target translation @target is newer than the creation timestamp of the source translation @source for translatable created field.',
|
||||
new FormattableMarkup('Translation creation timestamp of the target translation @target is newer than the creation timestamp of the source translation @source for translatable created field.',
|
||||
['@target' => $langcode, '@source' => $default_langcode]));
|
||||
}
|
||||
else {
|
||||
|
@ -176,7 +173,7 @@ abstract class ContentTranslationUITestBase extends ContentTranslationTestBase {
|
|||
$add_url = Url::fromRoute("entity.$entity_type_id.content_translation_add", [
|
||||
$entity->getEntityTypeId() => $entity->id(),
|
||||
'source' => $default_langcode,
|
||||
'target' => $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.
|
||||
|
@ -190,7 +187,7 @@ abstract class ContentTranslationUITestBase extends ContentTranslationTestBase {
|
|||
$add_url = Url::fromRoute("entity.$entity_type_id.content_translation_add", [
|
||||
$entity->getEntityTypeId() => $entity->id(),
|
||||
'source' => $source_langcode,
|
||||
'target' => $langcode
|
||||
'target' => $langcode,
|
||||
], ['language' => $language]);
|
||||
$this->drupalPostForm($add_url, $edit, $this->getFormSubmitActionForNewTranslation($entity, $langcode));
|
||||
$storage->resetCache([$this->entityId]);
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
<?php
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Tests the untranslatable fields behaviors.
|
||||
*
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationUntranslatableFieldsTest extends ContentTranslationPendingRevisionTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['field_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Configure one field as untranslatable.
|
||||
$this->drupalLogin($this->administrator);
|
||||
$edit = [
|
||||
'settings[' . $this->entityTypeId . '][' . $this->bundle . '][fields][' . $this->fieldName . ']' => 0,
|
||||
];
|
||||
$this->drupalPostForm('admin/config/regional/content-language', $edit, 'Save configuration');
|
||||
|
||||
/** @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() {
|
||||
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();
|
||||
entity_get_form_display($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() {
|
||||
/** @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->assertNotEmpty($this->xpath($field_xpath));
|
||||
$clue_xpath = '//label[@for="edit-' . strtr($this->fieldName, '_', '-') . '-0-value"]/span[text()="(all languages)"]';
|
||||
$this->assertEmpty($this->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->assertNotEmpty($this->xpath($field_xpath));
|
||||
$this->assertNotEmpty($this->xpath($clue_xpath));
|
||||
$this->assertSession()->pageTextContains('Untranslatable-but-visible test field');
|
||||
$this->drupalPostForm(NULL, [], 'Save');
|
||||
|
||||
// Check that the widget is displayed along with its clue in the edit form
|
||||
// for both languages.
|
||||
$this->drupalGet($en_edit_url);
|
||||
$this->assertNotEmpty($this->xpath($field_xpath));
|
||||
$this->assertNotEmpty($this->xpath($clue_xpath));
|
||||
$it_edit_url = $entity->toUrl('edit-form', ['language' => ConfigurableLanguage::load('it')]);
|
||||
$this->drupalGet($it_edit_url);
|
||||
$this->assertNotEmpty($this->xpath($field_xpath));
|
||||
$this->assertNotEmpty($this->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->drupalPostForm($settings_url, [$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->assertNotEmpty($this->xpath($field_xpath));
|
||||
$this->assertEmpty($this->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->assertEmpty($this->xpath($field_xpath));
|
||||
$this->assertEmpty($this->xpath($clue_xpath));
|
||||
$this->assertSession()->pageTextContains('Untranslatable-but-visible test field');
|
||||
|
||||
// Verify a warning is displayed.
|
||||
$this->assertSession()->pageTextContains('Fields that apply to all languages are hidden to avoid conflicting changes.');
|
||||
$edit_path = $entity->toUrl('edit-form')->toString();
|
||||
$link_xpath = '//a[@href=:edit_path and text()="Edit them on the original language form"]';
|
||||
$elements = $this->xpath($link_xpath, [':edit_path' => $edit_path]);
|
||||
$this->assertNotEmpty($elements);
|
||||
|
||||
// Configure untranslatable field widgets to be displayed on non-default
|
||||
// language edit forms.
|
||||
$this->drupalPostForm($settings_url, [$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->assertNotEmpty($this->xpath($field_xpath));
|
||||
$this->assertNotEmpty($this->xpath($clue_xpath));
|
||||
$this->drupalGet($it_edit_url);
|
||||
$this->assertNotEmpty($this->xpath($field_xpath));
|
||||
$this->assertNotEmpty($this->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->assertEmpty($this->xpath($field_xpath));
|
||||
$this->assertEmpty($this->xpath($clue_xpath));
|
||||
|
||||
// Verify a warning is displayed.
|
||||
$this->assertSession()->pageTextContains('Fields that apply to all languages are hidden to avoid conflicting changes.');
|
||||
$elements = $this->xpath($link_xpath, [':edit_path' => $edit_path]);
|
||||
$this->assertNotEmpty($elements);
|
||||
|
||||
// Verify that checkboxes on the language content settings page are checked
|
||||
// and disabled for moderated bundles.
|
||||
$this->drupalGet($settings_url);
|
||||
$input_xpath = '//input[@name="settings[' . $this->entityTypeId . '][' . $this->bundle . '][settings][content_translation][untranslatable_fields_hide]" and @value=1 and @disabled="disabled"]';
|
||||
$elements = $this->xpath($input_xpath);
|
||||
$this->assertNotEmpty($elements);
|
||||
$this->drupalPostForm(NULL, [$settings_key => 0], 'Save configuration');
|
||||
$elements = $this->xpath($input_xpath);
|
||||
$this->assertNotEmpty($elements);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,10 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_translation\Tests;
|
||||
namespace Drupal\Tests\content_translation\Functional;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait;
|
||||
use Drupal\user\UserInterface;
|
||||
|
||||
/**
|
||||
|
@ -14,6 +15,8 @@ use Drupal\user\UserInterface;
|
|||
*/
|
||||
class ContentTranslationWorkflowsTest extends ContentTranslationTestBase {
|
||||
|
||||
use AssertPageCacheContextsAndTagsTrait;
|
||||
|
||||
/**
|
||||
* The entity used for testing.
|
||||
*
|
||||
|
@ -158,19 +161,19 @@ class ContentTranslationWorkflowsTest extends ContentTranslationTestBase {
|
|||
// Check whether the user is allowed to access the entity form in edit mode.
|
||||
$edit_url = $this->entity->urlInfo('edit-form', $options);
|
||||
$this->drupalGet($edit_url, $options);
|
||||
$this->assertResponse($expected_status['edit'], SafeMarkup::format('The @user_label has the expected edit access.', $args));
|
||||
$this->assertResponse($expected_status['edit'], new FormattableMarkup('The @user_label has the expected edit access.', $args));
|
||||
|
||||
// Check whether the user is allowed to access the entity delete form.
|
||||
$delete_url = $this->entity->urlInfo('delete-form', $options);
|
||||
$this->drupalGet($delete_url, $options);
|
||||
$this->assertResponse($expected_status['delete'], SafeMarkup::format('The @user_label has the expected delete access.', $args));
|
||||
$this->assertResponse($expected_status['delete'], new FormattableMarkup('The @user_label has the expected delete access.', $args));
|
||||
|
||||
// Check whether the user is allowed to access the translation overview.
|
||||
$langcode = $this->langcodes[1];
|
||||
$options['language'] = $languages[$langcode];
|
||||
$translations_url = $this->entity->url('drupal:content-translation-overview', $options);
|
||||
$this->drupalGet($translations_url);
|
||||
$this->assertResponse($expected_status['overview'], SafeMarkup::format('The @user_label has the expected translation overview access.', $args));
|
||||
$this->assertResponse($expected_status['overview'], new FormattableMarkup('The @user_label has the expected translation overview access.', $args));
|
||||
|
||||
// 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);
|
||||
|
@ -186,7 +189,7 @@ class ContentTranslationWorkflowsTest extends ContentTranslationTestBase {
|
|||
else {
|
||||
$this->drupalGet($add_translation_url);
|
||||
}
|
||||
$this->assertResponse($expected_status['add_translation'], SafeMarkup::format('The @user_label has the expected translation creation access.', $args));
|
||||
$this->assertResponse($expected_status['add_translation'], new FormattableMarkup('The @user_label has the expected translation creation access.', $args));
|
||||
|
||||
// Check whether the user is allowed to edit a translation.
|
||||
$langcode = $this->langcodes[2];
|
||||
|
@ -214,7 +217,7 @@ class ContentTranslationWorkflowsTest extends ContentTranslationTestBase {
|
|||
else {
|
||||
$this->drupalGet($edit_translation_url);
|
||||
}
|
||||
$this->assertResponse($expected_status['edit_translation'], SafeMarkup::format('The @user_label has the expected translation edit access.', $args));
|
||||
$this->assertResponse($expected_status['edit_translation'], new FormattableMarkup('The @user_label has the expected translation edit access.', $args));
|
||||
|
||||
// Check whether the user is allowed to delete a translation.
|
||||
$langcode = $this->langcodes[2];
|
||||
|
@ -242,7 +245,7 @@ class ContentTranslationWorkflowsTest extends ContentTranslationTestBase {
|
|||
else {
|
||||
$this->drupalGet($delete_translation_url);
|
||||
}
|
||||
$this->assertResponse($expected_status['delete_translation'], SafeMarkup::format('The @user_label has the expected translation deletion access.', $args));
|
||||
$this->assertResponse($expected_status['delete_translation'], new FormattableMarkup('The @user_label has the expected translation deletion access.', $args));
|
||||
}
|
||||
|
||||
/**
|
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\content_translation\Functional\Update;
|
||||
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
|
||||
use Drupal\Tests\system\Functional\Entity\Traits\EntityDefinitionTestTrait;
|
||||
|
||||
/**
|
||||
* Tests the upgrade path for the Content Translation module.
|
||||
*
|
||||
* @group Update
|
||||
* @group legacy
|
||||
*/
|
||||
class ContentTranslationUpdateTest extends UpdatePathTestBase {
|
||||
|
||||
use EntityDefinitionTestTrait;
|
||||
|
||||
/**
|
||||
* The database connection used.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected $database;
|
||||
|
||||
/**
|
||||
* The entity definition update manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface
|
||||
*/
|
||||
protected $entityDefinitionUpdateManager;
|
||||
|
||||
/**
|
||||
* The entity manager service.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* The state service.
|
||||
*
|
||||
* @var \Drupal\Core\State\StateInterface
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->database = \Drupal::database();
|
||||
$this->entityDefinitionUpdateManager = \Drupal::entityDefinitionUpdateManager();
|
||||
$this->entityManager = \Drupal::entityManager();
|
||||
$this->state = \Drupal::state();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setDatabaseDumpFiles() {
|
||||
$this->databaseDumpFiles = [
|
||||
__DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.0.0-rc1-filled.standard.entity_test_update_mul.php.gz',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that initial values for metadata fields are populated correctly.
|
||||
*/
|
||||
public function testContentTranslationUpdate8400() {
|
||||
$this->updateEntityTypeToTranslatable();
|
||||
|
||||
// The test database dump contains NULL values for
|
||||
// 'content_translation_source', 'content_translation_outdated' and
|
||||
// 'content_translation_status' for the first 50 test entities.
|
||||
// @see _entity_test_update_create_test_entities()
|
||||
$first_entity_record = $this->database->select('entity_test_update_data', 'etud')
|
||||
->fields('etud')
|
||||
->condition('etud.id', 1)
|
||||
->execute()
|
||||
->fetchAllAssoc('id');
|
||||
$this->assertNull($first_entity_record[1]->content_translation_source);
|
||||
$this->assertNull($first_entity_record[1]->content_translation_outdated);
|
||||
$this->assertNull($first_entity_record[1]->content_translation_status);
|
||||
|
||||
$this->runUpdates();
|
||||
|
||||
// After running the updates, all those fields should be populated with
|
||||
// their default values.
|
||||
$first_entity_record = $this->database->select('entity_test_update_data', 'etud')
|
||||
->fields('etud')
|
||||
->condition('etud.id', 1)
|
||||
->execute()
|
||||
->fetchAllAssoc('id');
|
||||
$this->assertEqual(LanguageInterface::LANGCODE_NOT_SPECIFIED, $first_entity_record[1]->content_translation_source);
|
||||
$this->assertEqual(0, $first_entity_record[1]->content_translation_outdated);
|
||||
$this->assertEqual(1, $first_entity_record[1]->content_translation_status);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_translation\Tests\Views;
|
||||
namespace Drupal\Tests\content_translation\Functional\Views;
|
||||
|
||||
use Drupal\views_ui\Tests\UITestBase;
|
||||
use Drupal\Tests\views_ui\Functional\UITestBase;
|
||||
|
||||
/**
|
||||
* Tests the views UI when content_translation is enabled.
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\content_translation\Tests\Views;
|
||||
namespace Drupal\Tests\content_translation\Functional\Views;
|
||||
|
||||
use Drupal\content_translation\Tests\ContentTranslationTestBase;
|
||||
use Drupal\Tests\content_translation\Functional\ContentTranslationTestBase;
|
||||
use Drupal\views\Tests\ViewTestData;
|
||||
use Drupal\Core\Language\Language;
|
||||
use Drupal\user\Entity\User;
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
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}
|
||||
*/
|
||||
public static $modules = ['content_translation', 'contextual', 'node'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Set up an additional language.
|
||||
ConfigurableLanguage::createFromLangcode('es')->save();
|
||||
|
||||
// Create a content type.
|
||||
$this->drupalCreateContentType(['type' => 'page']);
|
||||
|
||||
// Enable content translation.
|
||||
$this->drupalLogin($this->rootUser);
|
||||
$this->drupalGet('admin/config/regional/content-language');
|
||||
$edit = [
|
||||
'entity_types[node]' => TRUE,
|
||||
'settings[node][page][translatable]' => TRUE,
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, t('Save configuration'));
|
||||
$this->drupalLogout();
|
||||
|
||||
// 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() {
|
||||
$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->assertContains('node/1/translations', $link->getAttribute('href'));
|
||||
}
|
||||
|
||||
}
|
|
@ -33,6 +33,7 @@ class ContentTranslationConfigImportTest extends KernelTestBase {
|
|||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->installConfig(['system']);
|
||||
$this->installEntitySchema('entity_test_mul');
|
||||
$this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.sync'));
|
||||
|
||||
|
@ -74,7 +75,7 @@ class ContentTranslationConfigImportTest extends KernelTestBase {
|
|||
'langcode' => 'en',
|
||||
'status' => TRUE,
|
||||
'dependencies' => [
|
||||
'module' => ['content_translation']
|
||||
'module' => ['content_translation'],
|
||||
],
|
||||
'id' => $config_id,
|
||||
'target_entity_type_id' => 'entity_test_mul',
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\content_translation\Kernel;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\entity_test\Entity\EntityTestMul;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
|
||||
/**
|
||||
* Tests the Content Translation bundle info logic.
|
||||
*
|
||||
* @group content_translation
|
||||
*/
|
||||
class ContentTranslationEntityBundleInfoTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['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() {
|
||||
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() {
|
||||
$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() {
|
||||
$entity = EntityTestMul::create();
|
||||
$entity->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $translation */
|
||||
$translation = $entity->addTranslation('it');
|
||||
$translation->save();
|
||||
|
||||
$this->assertTrue($entity->isTranslatable());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,482 @@
|
|||
<?php
|
||||
|
||||
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}
|
||||
*/
|
||||
public static $modules = ['file', 'image', 'language', 'content_translation', 'simpletest', '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() {
|
||||
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->entityManager->clearCachedDefinitions();
|
||||
|
||||
$this->contentTranslationManager = $this->container->get('content_translation.manager');
|
||||
$this->contentTranslationManager->setEnabled($entity_type_id, $entity_type_id, TRUE);
|
||||
|
||||
$this->storage = $this->entityManager->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() {
|
||||
// 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']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets untranslatable field widgets' display status.
|
||||
*
|
||||
* @param bool $display
|
||||
* Whether untranslatable field widgets should be displayed.
|
||||
*/
|
||||
protected function setUntranslatableFieldWidgetsDisplay($display) {
|
||||
$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
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
protected function assertViolations(EntityConstraintViolationListInterface $violations) {
|
||||
$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 $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)
|
||||
*/
|
||||
protected function assertLatestRevisionFieldValues($entity_id, array $expected_values) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
$entity = $this->storage->loadRevision($this->storage->getLatestRevisionId($entity_id));
|
||||
@list($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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -59,7 +59,7 @@ class ContentTranslationSyncUnitTest extends KernelTestBase {
|
|||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->synchronizer = new FieldTranslationSynchronizer($this->container->get('entity.manager'));
|
||||
$this->synchronizer = new FieldTranslationSynchronizer($this->container->get('entity.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'];
|
||||
|
@ -181,13 +181,21 @@ class ContentTranslationSyncUnitTest extends KernelTestBase {
|
|||
// their delta.
|
||||
$delta_callbacks = [
|
||||
// Continuous field values: all values are equal.
|
||||
function($delta) { return TRUE; },
|
||||
function ($delta) {
|
||||
return TRUE;
|
||||
},
|
||||
// Alternated field values: only the even ones are equal.
|
||||
function($delta) { return $delta % 2 !== 0; },
|
||||
function ($delta) {
|
||||
return $delta % 2 !== 0;
|
||||
},
|
||||
// Sparse field values: only the "middle" ones are equal.
|
||||
function($delta) { return $delta === 1 || $delta === 2; },
|
||||
function ($delta) {
|
||||
return $delta === 1 || $delta === 2;
|
||||
},
|
||||
// Sparse field values: only the "extreme" ones are equal.
|
||||
function($delta) { return $delta === 0 || $delta === 3; },
|
||||
function ($delta) {
|
||||
return $delta === 0 || $delta === 3;
|
||||
},
|
||||
];
|
||||
|
||||
foreach ($delta_callbacks as $delta_callback) {
|
||||
|
@ -241,7 +249,7 @@ class ContentTranslationSyncUnitTest extends KernelTestBase {
|
|||
for ($delta = 0; $delta < $this->cardinality; $delta++) {
|
||||
foreach ($this->columns as $column) {
|
||||
// If the column is synchronized, the value should have been synced,
|
||||
// for unsychronized columns, the value must not change.
|
||||
// for unsynchronized columns, the value must not change.
|
||||
$expected_value = in_array($column, $this->synchronized) ? $changed_items[$delta][$column] : $this->unchangedFieldValues[$langcode][$delta][$column];
|
||||
$this->assertEqual($field_values[$langcode][$delta][$column], $expected_value, "Differing Item $delta column $column for langcode $langcode synced correctly");
|
||||
}
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
<?php
|
||||
|
||||
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}
|
||||
*/
|
||||
public static $modules = [
|
||||
'content_translation',
|
||||
'language',
|
||||
'menu_ui',
|
||||
// Required for translation migrations.
|
||||
'migrate_drupal_multilingual',
|
||||
'node',
|
||||
'taxonomy',
|
||||
];
|
||||
|
||||
/**
|
||||
* The cached taxonomy tree items, keyed by vid and tid.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $treeData = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('taxonomy_term');
|
||||
$this->installConfig(static::$modules);
|
||||
$this->executeMigrations([
|
||||
'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.
|
||||
*/
|
||||
protected function assertEntity($id, $expected_language, $expected_label, $expected_vid, $expected_description = '', $expected_format = NULL, $expected_weight = 0, $expected_parents = [], $expected_field_integer_value = NULL, $expected_term_reference_tid = NULL) {
|
||||
/** @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.
|
||||
*/
|
||||
protected function assertHierarchy($vid, $tid, array $parent_ids) {
|
||||
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];
|
||||
$this->assertEquals($parent_ids, array_filter($term->parents), "Term $tid has correct parents in taxonomy tree");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the Drupal 6 i18n taxonomy term to Drupal 8 migration.
|
||||
*/
|
||||
public function testTranslatedTaxonomyTerms() {
|
||||
$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,99 @@
|
|||
<?php
|
||||
|
||||
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}
|
||||
*/
|
||||
public static $modules = [
|
||||
'comment',
|
||||
'content_translation',
|
||||
'language',
|
||||
'menu_ui',
|
||||
// Required for translation migrations.
|
||||
'migrate_drupal_multilingual',
|
||||
'node',
|
||||
'taxonomy',
|
||||
'text',
|
||||
'user',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
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([
|
||||
'd7_comment_type',
|
||||
'd7_node_type',
|
||||
'd7_taxonomy_vocabulary',
|
||||
'd7_entity_translation_settings',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests entity translation settings migration.
|
||||
*/
|
||||
public function testEntityTranslationSettingsMigration() {
|
||||
// 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,254 @@
|
|||
<?php
|
||||
|
||||
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}
|
||||
*/
|
||||
public static $modules = [
|
||||
'content_translation',
|
||||
'language',
|
||||
'migrate_drupal',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -49,20 +49,30 @@ class ContentTranslationLocalTasksTest extends LocalTaskIntegrationTestBase {
|
|||
*/
|
||||
public 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',
|
||||
]]],
|
||||
[
|
||||
[
|
||||
'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 a new issue