Issue #2949710 by plach, catch: Pending revisions may become unavailable when untranslatable fields affect all translations

8.6.x
effulgentsia 2018-03-05 19:54:48 -08:00
parent 787ca247df
commit 5fd118611a
7 changed files with 205 additions and 34 deletions

View File

@ -6,6 +6,7 @@
*/
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;
@ -115,10 +116,13 @@ function _content_translation_form_language_content_settings_form_alter(array &$
// 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' => !empty($settings['untranslatable_fields_hide']),
'#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]"]' => [

View File

@ -6,6 +6,7 @@
*/
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;
@ -177,9 +178,13 @@ function content_translation_entity_bundle_info_alter(&$bundles) {
foreach ($bundles as $entity_type_id => &$info) {
foreach ($info as $bundle => &$bundle_info) {
$bundle_info['translatable'] = $content_translation_manager->isEnabled($entity_type_id, $bundle);
if ($content_translation_manager instanceof BundleTranslationSettingsInterface) {
if ($bundle_info['translatable'] && $content_translation_manager instanceof BundleTranslationSettingsInterface) {
$settings = $content_translation_manager->getBundleTranslationSettings($entity_type_id, $bundle);
$bundle_info['untranslatable_fields.default_translation_affected'] = !empty($settings['untranslatable_fields_hide']);
// 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);
}
}
}

View File

@ -4,6 +4,7 @@ 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.
@ -148,6 +149,11 @@ class ContentTranslationManager implements ContentTranslationManagerInterface, B
/**
* 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.
*
@ -158,8 +164,29 @@ class ContentTranslationManager implements ContentTranslationManagerInterface, B
*
* @see https://www.drupal.org/node/2940575
*/
public static function isPendingRevisionSupportEnabled() {
return \Drupal::moduleHandler()->moduleExists('content_moderation');
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;
}
}

View File

@ -88,7 +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();
$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.
@ -351,7 +351,7 @@ class ContentTranslationController extends ControllerBase {
// 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()) {
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());

View File

@ -56,7 +56,7 @@ class ContentTranslationRouteSubscriber extends RouteSubscriberBase {
}
$path = $base_path . '/translations';
$load_latest_revision = ContentTranslationManager::isPendingRevisionSupportEnabled();
$load_latest_revision = ContentTranslationManager::isPendingRevisionSupportEnabled($entity_type_id);
$route = new Route(
$path,

View File

@ -0,0 +1,114 @@
<?php
namespace Drupal\Tests\content_translation\Functional;
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
/**
* Base class for pending revision translation tests.
*/
abstract class ContentTranslationPendingRevisionTestBase extends ContentTranslationTestBase {
use ContentTypeCreationTrait;
/**
* {@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]);
}
}

View File

@ -10,14 +10,7 @@ use Drupal\language\Entity\ConfigurableLanguage;
*
* @group content_translation
*/
class ContentTranslationUntranslatableFieldsTest extends ContentTranslationTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['language', 'content_translation', 'entity_test'];
class ContentTranslationUntranslatableFieldsTest extends ContentTranslationPendingRevisionTestBase {
/**
* {@inheritdoc}
@ -39,20 +32,13 @@ class ContentTranslationUntranslatableFieldsTest extends ContentTranslationTestB
$this->assertFalse($definitions[$this->fieldName]->isTranslatable());
}
/**
* {@inheritdoc}
*/
protected function getEditorPermissions() {
return array_merge(parent::getTranslatorPermissions(), ['administer entity_test content', 'view test entity']);
}
/**
* 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([], 'en');
$id = $this->createEntity(['title' => $this->randomString()], 'en');
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $entity_type_manager
->getStorage($this->entityTypeId)
@ -60,7 +46,8 @@ class ContentTranslationUntranslatableFieldsTest extends ContentTranslationTestB
// Check that the untranslatable field widget is displayed on the edit form
// and no translatability clue is displayed yet.
$this->drupalGet($entity->toUrl('edit-form'));
$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)"]';
@ -81,30 +68,29 @@ class ContentTranslationUntranslatableFieldsTest extends ContentTranslationTestB
// Check that the widget is displayed along with its clue in the edit form
// for both languages.
$this->drupalGet($entity->toUrl('edit-form'));
$this->drupalGet($en_edit_url);
$this->assertNotEmpty($this->xpath($field_xpath));
$this->assertNotEmpty($this->xpath($clue_xpath));
$it_language = ConfigurableLanguage::load('it');
$this->drupalGet($entity->toUrl('edit-form', ['language' => $it_language]));
$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.
$edit = [
'settings[' . $this->entityTypeId . '][' . $this->bundle . '][settings][content_translation][untranslatable_fields_hide]' => 1,
];
$this->drupalPostForm('admin/config/regional/content-language', $edit, 'Save configuration');
$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($entity->toUrl('edit-form'));
$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));
// Verify no widget is displayed on the non-default language edit form.
$this->drupalGet($entity->toUrl('edit-form', ['language' => $it_language]));
$this->drupalGet($it_edit_url);
$this->assertEmpty($this->xpath($field_xpath));
$this->assertEmpty($this->xpath($clue_xpath));
@ -114,6 +100,41 @@ class ContentTranslationUntranslatableFieldsTest extends ContentTranslationTestB
$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);
}
}