Core and composer updates

This commit is contained in:
Rob Davies 2017-07-03 16:47:07 +01:00
parent a82634bb98
commit 62cac30480
1118 changed files with 21770 additions and 6306 deletions

View file

@ -3,6 +3,7 @@ status: true
dependencies:
module:
- aggregator
- user
id: aggregator_rss_feed
label: 'Aggregator RSS feed'
module: aggregator
@ -137,6 +138,8 @@ display:
- url.query_args
- user.permissions
cacheable: false
max-age: -1
tags: { }
feed_items:
display_plugin: feed
id: feed_items
@ -154,3 +157,5 @@ display:
- 'languages:language_interface'
- user.permissions
cacheable: false
max-age: -1
tags: { }

View file

@ -143,7 +143,8 @@ display:
- 'languages:language_interface'
- url.query_args
- user.permissions
max-age: 0
max-age: -1
tags: { }
feed_1:
display_plugin: feed
id: feed_1
@ -401,7 +402,8 @@ display:
- 'languages:language_content'
- 'languages:language_interface'
- user.permissions
max-age: 0
max-age: -1
tags: { }
page_1:
display_plugin: page
id: page_1
@ -423,4 +425,5 @@ display:
- 'languages:language_interface'
- url.query_args
- user.permissions
max-age: 0
max-age: -1
tags: { }

View file

@ -7,7 +7,7 @@ source:
process:
iid: iid
fid:
plugin: migration
plugin: migration_lookup
migration: d6_aggregator_feed
source: fid
title: title

View file

@ -7,7 +7,7 @@ source:
process:
iid: iid
fid:
plugin: migration
plugin: migration_lookup
migration: d7_aggregator_feed
source: fid
title: title

View file

@ -60,4 +60,14 @@ class FeedAdminDisplayTest extends AggregatorTestBase {
$this->assertNoText('left', 'The feed is not scheduled. It does not show a timeframe "x x left" for next update.');
}
/**
* {@inheritdoc}
*/
public function randomMachineName($length = 8) {
$value = parent::randomMachineName($length);
// See expected values in testFeedUpdateFields().
$value = str_replace(['never', 'imminently', 'ago', 'left'], 'x', $value);
return $value;
}
}

View file

@ -59,29 +59,29 @@ class BigPipePlaceholderTestCases {
],
]
);
$status_messages->bigPipePlaceholderId = 'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args[0]&token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA';
$status_messages->bigPipePlaceholderId = 'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args%5B0%5D&token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA';
$status_messages->bigPipePlaceholderRenderArray = [
'#markup' => '<span data-big-pipe-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args[0]&amp;token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"></span>',
'#markup' => '<span data-big-pipe-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args%5B0%5D&amp;token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"></span>',
'#cache' => $cacheability_depends_on_session_and_nojs_cookie,
'#attached' => [
'library' => ['big_pipe/big_pipe'],
'drupalSettings' => [
'bigPipePlaceholderIds' => [
'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args[0]&token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA' => TRUE,
'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args%5B0%5D&token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA' => TRUE,
],
],
'big_pipe_placeholders' => [
'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args[0]&amp;token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA' => $status_messages->placeholderRenderArray,
'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args%5B0%5D&amp;token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA' => $status_messages->placeholderRenderArray,
],
],
];
$status_messages->bigPipeNoJsPlaceholder = '<span data-big-pipe-nojs-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args[0]&amp;token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"></span>';
$status_messages->bigPipeNoJsPlaceholder = '<span data-big-pipe-nojs-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args%5B0%5D&amp;token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"></span>';
$status_messages->bigPipeNoJsPlaceholderRenderArray = [
'#markup' => '<span data-big-pipe-nojs-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args[0]&amp;token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"></span>',
'#markup' => '<span data-big-pipe-nojs-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args%5B0%5D&amp;token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"></span>',
'#cache' => $cacheability_depends_on_session_and_nojs_cookie,
'#attached' => [
'big_pipe_nojs_placeholders' => [
'<span data-big-pipe-nojs-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args[0]&amp;token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"></span>' => $status_messages->placeholderRenderArray,
'<span data-big-pipe-nojs-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args%5B0%5D&amp;token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"></span>' => $status_messages->placeholderRenderArray,
],
],
];
@ -109,7 +109,7 @@ class BigPipePlaceholderTestCases {
[
'command' => 'insert',
'method' => 'replaceWith',
'selector' => '[data-big-pipe-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args[0]&token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"]',
'selector' => '[data-big-pipe-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args%5B0%5D&token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"]',
'data' => "\n" . ' <div role="contentinfo" aria-label="Status message" class="messages messages--status">' . "\n" . ' <h2 class="visually-hidden">Status message</h2>' . "\n" . ' Hello from BigPipe!' . "\n" . ' </div>' . "\n ",
'settings' => NULL,
],
@ -272,24 +272,24 @@ class BigPipePlaceholderTestCases {
'#lazy_builder' => ['\Drupal\big_pipe_test\BigPipeTestController::exception', ['llamas', 'suck']],
]
);
$exception->bigPipePlaceholderId = 'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&amp;args[0]=llamas&amp;args[1]=suck&amp;token=uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU';
$exception->bigPipePlaceholderId = 'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&amp;args%5B0%5D=llamas&amp;args%5B1%5D=suck&amp;token=uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU';
$exception->bigPipePlaceholderRenderArray = [
'#markup' => '<span data-big-pipe-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&amp;args[0]=llamas&amp;args[1]=suck&amp;token=uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU"></span>',
'#markup' => '<span data-big-pipe-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&amp;args%5B0%5D=llamas&amp;args%5B1%5D=suck&amp;token=uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU"></span>',
'#cache' => $cacheability_depends_on_session_and_nojs_cookie,
'#attached' => [
'library' => ['big_pipe/big_pipe'],
'drupalSettings' => [
'bigPipePlaceholderIds' => [
'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&args[0]=llamas&args[1]=suck&token=uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU' => TRUE,
'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&args%5B0%5D=llamas&args%5B1%5D=suck&token=uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU' => TRUE,
],
],
'big_pipe_placeholders' => [
'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&amp;args[0]=llamas&amp;args[1]=suck&amp;token=uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU' => $exception->placeholderRenderArray,
'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&amp;args%5B0%5D=llamas&amp;args%5B1%5D=suck&amp;token=uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU' => $exception->placeholderRenderArray,
],
],
];
$exception->embeddedAjaxResponseCommands = NULL;
$exception->bigPipeNoJsPlaceholder = '<span data-big-pipe-nojs-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&amp;args[0]=llamas&amp;args[1]=suck&amp;token=uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU"></span>';
$exception->bigPipeNoJsPlaceholder = '<span data-big-pipe-nojs-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&amp;args%5B0%5D=llamas&amp;args%5B1%5D=suck&amp;token=uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU"></span>';
$exception->bigPipeNoJsPlaceholderRenderArray = [
'#markup' => $exception->bigPipeNoJsPlaceholder,
'#cache' => $cacheability_depends_on_session_and_nojs_cookie,

View file

@ -293,7 +293,7 @@ class BigPipeTest extends WebTestBase {
// @see performMetaRefresh()
$this->drupalGet(Url::fromRoute('big_pipe_test_multi_occurrence'));
$big_pipe_placeholder_id = 'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args[0]&amp;token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA';
$big_pipe_placeholder_id = 'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args%5B0%5D&amp;token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA';
$expected_placeholder_replacement = '<script type="application/vnd.drupal-ajax" data-big-pipe-replacement-for-placeholder-with-id="' . $big_pipe_placeholder_id . '">';
$this->assertRaw('The count is 1.');
$this->assertNoRaw('The count is 2.');

View file

@ -13,7 +13,8 @@ process:
source: status
id:
# We need something unique, so aggregator, aggregator_1 etc will do.
plugin: dedupe_entity
plugin: make_unique_entity_field
entity_type: block
field: id
postfix: _

View file

@ -21,6 +21,12 @@ class MigrateBlockTest extends MigrateDrupal6TestBase {
'comment',
'menu_ui',
'block_content',
'taxonomy',
'node',
'aggregator',
'book',
'forum',
'statistics',
];
/**
@ -30,15 +36,14 @@ class MigrateBlockTest extends MigrateDrupal6TestBase {
parent::setUp();
// Install the themes used for this test.
$this->container->get('theme_installer')->install(['bartik', 'seven', 'test_theme']);
$this->container->get('theme_installer')->install(['bartik', 'test_theme']);
$this->installConfig(['block_content']);
$this->installEntitySchema('block_content');
// Set Bartik and Seven as the default public and admin theme.
// Set Bartik as the default public theme.
$config = $this->config('system.theme');
$config->set('default', 'bartik');
$config->set('admin', 'seven');
$config->save();
$this->executeMigrations([
@ -46,7 +51,6 @@ class MigrateBlockTest extends MigrateDrupal6TestBase {
'block_content_type',
'block_content_body_field',
'd6_custom_block',
'menu',
'd6_user_role',
'd6_block',
]);
@ -66,14 +70,12 @@ class MigrateBlockTest extends MigrateDrupal6TestBase {
* The theme.
* @param string $weight
* The block weight.
* @param string $label
* The block label.
* @param string $label_display
* The block label display setting.
* @param array $settings
* (optional) The block settings.
* @param bool $status
* Whether the block is expected to be enabled or disabled.
*/
public function assertEntity($id, $visibility, $region, $theme, $weight, $label, $label_display, $status = TRUE) {
public function assertEntity($id, $visibility, $region, $theme, $weight, array $settings = NULL, $status = TRUE) {
$block = Block::load($id);
$this->assertTrue($block instanceof Block);
$this->assertSame($visibility, $block->getVisibility());
@ -81,10 +83,11 @@ class MigrateBlockTest extends MigrateDrupal6TestBase {
$this->assertSame($theme, $block->getTheme());
$this->assertSame($weight, $block->getWeight());
$this->assertSame($status, $block->status());
$config = $this->config('block.block.' . $id);
$this->assertSame($label, $config->get('settings.label'));
$this->assertSame($label_display, $config->get('settings.label_display'));
if ($settings) {
$block_settings = $block->get('settings');
$block_settings['id'] = current(explode(':', $block_settings['id']));
$this->assertEquals($settings, $block_settings);
}
}
/**
@ -92,62 +95,209 @@ class MigrateBlockTest extends MigrateDrupal6TestBase {
*/
public function testBlockMigration() {
$blocks = Block::loadMultiple();
$this->assertIdentical(9, count($blocks));
$this->assertCount(14, $blocks);
// User blocks
$visibility = [];
$visibility['request_path']['id'] = 'request_path';
$visibility['request_path']['negate'] = TRUE;
$visibility['request_path']['pages'] = "<front>\n/node/1\n/blog/*";
$this->assertEntity('user', $visibility, 'sidebar_first', 'bartik', 0, '', '0');
$visibility = [];
$this->assertEntity('user_1', $visibility, 'sidebar_first', 'bartik', 0, '', '0');
$visibility['user_role']['id'] = 'user_role';
$roles['authenticated'] = 'authenticated';
$visibility['user_role']['roles'] = $roles;
$context_mapping['user'] = '@user.current_user_context:current_user';
$visibility['user_role']['context_mapping'] = $context_mapping;
$visibility['user_role']['negate'] = FALSE;
$this->assertEntity('user_2', $visibility, 'sidebar_second', 'bartik', -9, '', '0');
$visibility = [];
$visibility['user_role']['id'] = 'user_role';
$visibility['user_role']['roles'] = [
'migrate_test_role_1' => 'migrate_test_role_1'
// Check user blocks.
$visibility = [
'request_path' => [
'id' => 'request_path',
'negate' => TRUE,
'pages' => "<front>\n/node/1\n/blog/*",
],
];
$context_mapping['user'] = '@user.current_user_context:current_user';
$visibility['user_role']['context_mapping'] = $context_mapping;
$visibility['user_role']['negate'] = FALSE;
$this->assertEntity('user_3', $visibility, 'sidebar_second', 'bartik', -6, '', '0');
$settings = [
'id' => 'user_login_block',
'label' => '',
'provider' => 'user',
'label_display' => '0',
];
$this->assertEntity('user', $visibility, 'sidebar_first', 'bartik', -10, $settings);
// Check system block
$visibility = [];
$visibility['request_path']['id'] = 'request_path';
$visibility['request_path']['negate'] = TRUE;
$visibility['request_path']['pages'] = '/node/1';
$this->assertEntity('system', $visibility, 'footer_fifth', 'bartik', -5, '', '0');
$settings = [
'id' => 'system_menu_block',
'label' => '',
'provider' => 'system',
'label_display' => '0',
'level' => 1,
'depth' => 0,
];
$this->assertEntity('user_1', $visibility, 'sidebar_first', 'bartik', -11, $settings);
// Check menu blocks
$visibility = [];
$this->assertEntity('menu', $visibility, 'header', 'bartik', -5, '', '0');
$visibility = [
'user_role' => [
'id' => 'user_role',
'roles' => [
'authenticated' => 'authenticated',
],
'context_mapping' => [
'user' => '@user.current_user_context:current_user',
],
'negate' => FALSE,
],
];
$settings = [
'id' => 'broken',
'label' => '',
'provider' => 'core',
'label_display' => '0',
'items_per_page' => '5',
];
$this->assertEntity('user_2', $visibility, 'sidebar_second', 'bartik', -11, $settings);
// Check custom blocks
$visibility['request_path']['id'] = 'request_path';
$visibility['request_path']['negate'] = FALSE;
$visibility['request_path']['pages'] = '<front>';
$this->assertEntity('block', $visibility, 'content', 'bartik', 0, 'Static Block', 'visible');
$visibility = [
'user_role' => [
'id' => 'user_role',
'roles' => [
'migrate_test_role_1' => 'migrate_test_role_1',
],
'context_mapping' => [
'user' => '@user.current_user_context:current_user',
],
'negate' => FALSE,
],
];
$settings = [
'id' => 'broken',
'label' => '',
'provider' => 'core',
'label_display' => '0',
'items_per_page' => '10',
];
$this->assertEntity('user_3', $visibility, 'sidebar_second', 'bartik', -10, $settings);
$visibility['request_path']['id'] = 'request_path';
$visibility['request_path']['negate'] = FALSE;
$visibility['request_path']['pages'] = '/node';
// Check system block.
$visibility = [
'request_path' => [
'id' => 'request_path',
'negate' => TRUE,
'pages' => '/node/1',
],
];
$settings = [
'id' => 'system_powered_by_block',
'label' => '',
'provider' => 'system',
'label_display' => '0',
];
$this->assertEntity('system', $visibility, 'footer_fifth', 'bartik', -5, $settings);
// Check menu blocks.
$settings = [
'id' => 'broken',
'label' => '',
'provider' => 'core',
'label_display' => '0',
];
$this->assertEntity('menu', [], 'header', 'bartik', -5, $settings);
// Check aggregator block.
$settings = [
'id' => 'aggregator_feed_block',
'label' => '',
'provider' => 'aggregator',
'label_display' => '0',
'block_count' => 7,
'feed' => '5',
];
$this->assertEntity('aggregator', [], 'sidebar_second', 'bartik', -2, $settings);
// Check book block.
$settings = [
'id' => 'book_navigation',
'label' => '',
'provider' => 'book',
'label_display' => '0',
'block_mode' => 'book pages',
];
$this->assertEntity('book', [], 'sidebar_second', 'bartik', -4, $settings);
// Check forum block settings.
$settings = [
'id' => 'forum_active_block',
'label' => '',
'provider' => 'forum',
'label_display' => '0',
'block_count' => 3,
'properties' => [
'administrative' => '1',
],
];
$this->assertEntity('forum', [], 'sidebar_first', 'bartik', -8, $settings);
$settings = [
'id' => 'forum_new_block',
'label' => '',
'provider' => 'forum',
'label_display' => '0',
'block_count' => 4,
'properties' => [
'administrative' => '1',
],
];
$this->assertEntity('forum_1', [], 'sidebar_first', 'bartik', -9, $settings);
// Check statistic block settings.
$settings = [
'id' => 'broken',
'label' => '',
'provider' => 'core',
'label_display' => '0',
'top_day_num' => 7,
'top_all_num' => 8,
'top_last_num' => 9,
];
$this->assertEntity('statistics', [], 'sidebar_second', 'bartik', 0, $settings);
// Check custom blocks.
$visibility = [
'request_path' => [
'id' => 'request_path',
'negate' => FALSE,
'pages' => '<front>',
],
];
$settings = [
'id' => 'block_content',
'label' => 'Static Block',
'provider' => 'block_content',
'label_display' => 'visible',
'status' => TRUE,
'info' => '',
'view_mode' => 'full',
];
$this->assertEntity('block', $visibility, 'content', 'bartik', 0, $settings);
$visibility = [
'request_path' => [
'id' => 'request_path',
'negate' => FALSE,
'pages' => '/node',
],
];
$settings = [
'id' => 'block_content',
'label' => 'Another Static Block',
'provider' => 'block_content',
'label_display' => 'visible',
'status' => TRUE,
'info' => '',
'view_mode' => 'full',
];
// We expect this block to be disabled because '' is not a valid region,
// and block_rebuild() will disable any block in an invalid region.
$this->assertEntity('block_1', $visibility, '', 'bluemarine', -4, 'Another Static Block', 'visible', FALSE);
$this->assertEntity('block_1', $visibility, '', 'bluemarine', -4, $settings, FALSE);
$visibility = [];
$this->assertEntity('block_2', $visibility, 'right', 'test_theme', -7, '', '0');
$settings = [
'id' => 'block_content',
'label' => '',
'provider' => 'block_content',
'label_display' => '0',
'status' => TRUE,
'info' => '',
'view_mode' => 'full',
];
$this->assertEntity('block_2', [], 'right', 'test_theme', -7, $settings);
// Custom block with php code is not migrated.
$block = Block::load('block_3');

View file

@ -18,7 +18,7 @@ function block_content_help($route_name, RouteMatchInterface $route_match) {
$field_ui = \Drupal::moduleHandler()->moduleExists('field_ui') ? \Drupal::url('help.page', ['name' => 'field_ui']) : '#';
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Custom Block module allows you to create and manage custom <em>block types</em> and <em>content-containing blocks</em> from the <a href = ":block-library" >Custom block library<a/> page. Custom block types have fields; see the <a href=":field-help">Field module help</a> for more information. Once created, custom blocks can be placed in regions just like blocks provided by other modules; see the <a href=":blocks">Block module help</a> page for details. For more information, see the <a href=":online-help">online documentation for the Custom Block module</a>.', [':block-library' => \Drupal::url('entity.block_content.collection'), ':block-content' => \Drupal::url('entity.block_content.collection'), ':field-help' => \Drupal::url('help.page', ['name' => 'field']), ':blocks' => \Drupal::url('help.page', ['name' => 'block']), ':online-help' => 'https://www.drupal.org/documentation/modules/block_content']) . '</p>';
$output .= '<p>' . t('The Custom Block module allows you to create and manage custom <em>block types</em> and <em>content-containing blocks</em> from the <a href = ":block-library" >Custom block library</a> page. Custom block types have fields; see the <a href=":field-help">Field module help</a> for more information. Once created, custom blocks can be placed in regions just like blocks provided by other modules; see the <a href=":blocks">Block module help</a> page for details. For more information, see the <a href=":online-help">online documentation for the Custom Block module</a>.', [':block-library' => \Drupal::url('entity.block_content.collection'), ':block-content' => \Drupal::url('entity.block_content.collection'), ':field-help' => \Drupal::url('help.page', ['name' => 'field']), ':blocks' => \Drupal::url('help.page', ['name' => 'block']), ':online-help' => 'https://www.drupal.org/documentation/modules/block_content']) . '</p>';
$output .= '<h3>' . t('Uses') . '</h3>';
$output .= '<dl>';
$output .= '<dt>' . t('Creating and managing custom block types') . '</dt>';

View file

@ -79,6 +79,7 @@ entity.block_content_type.edit_form:
path: '/admin/structure/block/block-content/manage/{block_content_type}'
defaults:
_entity_form: 'block_content_type.edit'
_title_callback: '\Drupal\Core\Entity\Controller\EntityController::title'
requirements:
_entity_access: 'block_content_type.update'

View file

@ -0,0 +1,39 @@
id: block_content_entity_display
label: Body field display configuration
migration_tags:
- Drupal 6
- Drupal 7
source:
plugin: embedded_data
data_rows:
-
entity_type: block_content
bundle: basic
view_mode: default
field_name: body
options:
label: hidden
ids:
entity_type:
type: string
bundle:
type: string
view_mode:
type: string
field_name:
type: string
process:
entity_type: entity_type
bundle: bundle
view_mode: view_mode
field_name: field_name
options: options
destination:
plugin: component_entity_display
migration_dependencies:
required:
- block_content_body_field
provider:
- block_content
- migrate_drupal

View file

@ -0,0 +1,36 @@
id: block_content_entity_form_display
label: Body field form display configuration
migration_tags:
- Drupal 6
- Drupal 7
source:
plugin: embedded_data
data_rows:
-
entity_type: block_content
bundle: basic
form_mode: default
field_name: body
ids:
entity_type:
type: string
bundle:
type: string
form_mode:
type: string
field_name:
type: string
process:
entity_type: entity_type
bundle: bundle
form_mode: form_mode
field_name: field_name
destination:
plugin: component_entity_form_display
migration_dependencies:
required:
- block_content_body_field
provider:
- block_content
- migrate_drupal

View file

@ -8,7 +8,7 @@ process:
id: bid
info: info
'body/format':
plugin: migration
plugin: migration_lookup
migration: d6_filter_format
source: format
'body/value': body

View file

@ -8,7 +8,7 @@ process:
id: bid
info: info
'body/format':
plugin: migration
plugin: migration_lookup
migration: d7_filter_format
source: format
'body/value': body

View file

@ -4,6 +4,8 @@ namespace Drupal\block_content\Tests;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\Component\Utility\Html;
use Drupal\Core\Url;
use Drupal\system\Tests\Menu\AssertBreadcrumbTrait;
/**
* Ensures that custom block type functions work correctly.
@ -12,6 +14,7 @@ use Drupal\Component\Utility\Html;
*/
class BlockContentTypeTest extends BlockContentTestBase {
use AssertBreadcrumbTrait;
/**
* Modules to enable.
*
@ -91,6 +94,7 @@ class BlockContentTypeTest extends BlockContentTestBase {
* Tests editing a block type using the UI.
*/
public function testBlockContentTypeEditing() {
$this->drupalPlaceBlock('system_breadcrumb_block');
// Now create an initial block-type.
$this->createBlockContentType('basic', TRUE);
@ -113,6 +117,13 @@ class BlockContentTypeTest extends BlockContentTestBase {
$this->drupalGet('admin/structure/block/block-content/manage/basic');
$this->assertTitle(format_string('Edit @type custom block type | Drupal', ['@type' => 'basic']));
$this->drupalPostForm(NULL, $edit, t('Save'));
$front_page_path = Url::fromRoute('<front>')->toString();
$this->assertBreadcrumb('admin/structure/block/block-content/manage/basic/fields', [
$front_page_path => 'Home',
'admin/structure/block' => 'Block layout',
'admin/structure/block/block-content' => 'Custom block library',
'admin/structure/block/block-content/manage/basic' => 'Bar',
]);
\Drupal::entityManager()->clearCachedFieldDefinitions();
$this->drupalGet('block/add');

View file

@ -0,0 +1,54 @@
<?php
namespace Drupal\Tests\block_content\Kernel\Migrate;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
* Tests migration of block content body field display configuration.
*
* @group block_content
*/
class MigrateBlockContentEntityDisplayTest extends MigrateDrupal7TestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['block', 'block_content', 'filter', 'text'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installConfig(static::$modules);
$this->executeMigrations([
'block_content_type',
'block_content_body_field',
'block_content_entity_display',
]);
}
/**
* Asserts a display entity.
*
* @param string $id
* The entity ID.
* @param string $component_id
* The ID of the display component.
*/
protected function assertDisplay($id, $component_id) {
$component = EntityViewDisplay::load($id)->getComponent($component_id);
$this->assertInternalType('array', $component);
$this->assertSame('hidden', $component['label']);
}
/**
* Tests the migrated display configuration.
*/
public function testMigration() {
$this->assertDisplay('block_content.basic.default', 'body');
}
}

View file

@ -0,0 +1,54 @@
<?php
namespace Drupal\Tests\block_content\Kernel\Migrate;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
* Tests migration of block content body field form display configuration.
*
* @group block_content
*/
class MigrateBlockContentEntityFormDisplayTest extends MigrateDrupal7TestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['block', 'block_content', 'filter', 'text'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installConfig(static::$modules);
$this->executeMigrations([
'block_content_type',
'block_content_body_field',
'block_content_entity_form_display',
]);
}
/**
* Asserts a display entity.
*
* @param string $id
* The entity ID.
* @param string $component
* The ID of the form component.
*/
protected function assertDisplay($id, $component_id) {
$component = EntityFormDisplay::load($id)->getComponent($component_id);
$this->assertInternalType('array', $component);
$this->assertSame('text_textarea_with_summary', $component['type']);
}
/**
* Tests the migrated display configuration.
*/
public function testMigration() {
$this->assertDisplay('block_content.basic.default', 'body');
}
}

View file

@ -14,7 +14,7 @@ process:
method: process
source: plid
-
plugin: migration
plugin: migration_lookup
migration: d6_book
destination:
plugin: book

View file

@ -33,7 +33,7 @@ class MigrateBookConfigsTest extends MigrateDrupal6TestBase {
public function testBookSettings() {
$config = $this->config('book.settings');
$this->assertIdentical('book', $config->get('child_type'));
$this->assertIdentical('all pages', $config->get('block.navigation.mode'));
$this->assertSame('book pages', $config->get('block.navigation.mode'));
$this->assertIdentical(['book'], $config->get('allowed_types'));
$this->assertConfigSchema(\Drupal::service('config.typed'), 'book.settings', $config->get());
}

View file

@ -15,7 +15,7 @@ class MigrateBookTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['book'];
public static $modules = ['book', 'menu_ui'];
/**
* {@inheritdoc}

View file

@ -21,7 +21,7 @@ use Drupal\editor\Entity\Editor;
* @param $plugins
* An array of all the existing plugin definitions, passed by reference.
*
* @see CKEditorPluginManager
* @see \Drupal\ckeditor\CKEditorPluginManager
*/
function hook_ckeditor_plugin_info_alter(array &$plugins) {
$plugins['someplugin']['label'] = t('Better name');

View file

@ -10,6 +10,7 @@ drupal.ckeditor:
- core/drupal
- core/drupalSettings
- core/drupal.debounce
- core/drupal.displace
- core/ckeditor
- editor/drupal.editor
# Ensure to run after core/matchmedia.

View file

@ -35,8 +35,9 @@ interface CKEditorPluginButtonsInterface extends CKEditorPluginInterface {
* @return array
* An array of buttons that are provided by this plugin. This will
* only be used in the administrative section for assembling the toolbar.
* Each button should by keyed by its CKEditor button name, and should
* contain an array of button properties, including:
* Each button should be keyed by its CKEditor button name (you can look up
* the button name up in the plugin.js file), and should contain an array of
* button properties, including:
* - label: A human-readable, translated button name.
* - image: An image for the button to be used in the toolbar.
* - image_rtl: If the image needs to have a right-to-left version, specify

View file

@ -1,19 +1,19 @@
<?php
namespace Drupal\ckeditor\Tests;
namespace Drupal\Tests\ckeditor\Functional;
use Drupal\Component\Serialization\Json;
use Drupal\editor\Entity\Editor;
use Drupal\filter\FilterFormatInterface;
use Drupal\simpletest\WebTestBase;
use Drupal\filter\Entity\FilterFormat;
use Drupal\Tests\BrowserTestBase;
/**
* Tests administration of CKEditor.
*
* @group ckeditor
*/
class CKEditorAdminTest extends WebTestBase {
class CKEditorAdminTest extends BrowserTestBase {
/**
* Modules to enable.
@ -62,12 +62,12 @@ class CKEditorAdminTest extends WebTestBase {
$select = $this->xpath('//select[@name="editor[editor]"]');
$select_is_disabled = $this->xpath('//select[@name="editor[editor]" and @disabled="disabled"]');
$options = $this->xpath('//select[@name="editor[editor]"]/option');
$this->assertTrue(count($select) === 1, 'The Text Editor select exists.');
$this->assertTrue(count($select_is_disabled) === 0, 'The Text Editor select is not disabled.');
$this->assertTrue(count($options) === 2, 'The Text Editor select has two options.');
$this->assertTrue(((string) $options[0]) === 'None', 'Option 1 in the Text Editor select is "None".');
$this->assertTrue(((string) $options[1]) === 'CKEditor', 'Option 2 in the Text Editor select is "CKEditor".');
$this->assertTrue(((string) $options[0]['selected']) === 'selected', 'Option 1 ("None") is selected.');
$this->assertCount(1, $select, 'The Text Editor select exists.');
$this->assertCount(0, $select_is_disabled, 'The Text Editor select is not disabled.');
$this->assertCount(2, $options, 'The Text Editor select has two options.');
$this->assertSame('None', $options[0]->getText(), 'Option 1 in the Text Editor select is "None".');
$this->assertSame('CKEditor', $options[1]->getText(), 'Option 2 in the Text Editor select is "CKEditor".');
$this->assertSame('selected', $options[0]->getAttribute('selected'), 'Option 1 ("None") is selected.');
// Select the "CKEditor" editor and click the "Save configuration" button.
$edit = [
@ -110,7 +110,7 @@ class CKEditorAdminTest extends WebTestBase {
$this->assertIdentical($this->castSafeStrings($ckeditor->getDefaultSettings()), $expected_default_settings);
// Keep the "CKEditor" editor selected and click the "Configure" button.
$this->drupalPostAjaxForm(NULL, $edit, 'editor_configure');
$this->drupalPostForm(NULL, $edit, 'editor_configure');
$editor = Editor::load('filtered_html');
$this->assertFalse($editor, 'No Editor config entity exists yet.');
@ -120,8 +120,10 @@ class CKEditorAdminTest extends WebTestBase {
'#editor' => Editor::create(['editor' => 'ckeditor']),
'#plugins' => $this->container->get('plugin.manager.ckeditor.plugin')->getButtons(),
];
$settings = $this->getDrupalSettings();
$expected = $settings['ckeditor']['toolbarAdmin'];
$this->assertEqual(
$this->drupalSettings['ckeditor']['toolbarAdmin'],
$expected,
$this->container->get('renderer')->renderPlain($ckeditor_settings_toolbar),
'CKEditor toolbar settings are rendered as part of drupalSettings.'
);
@ -230,12 +232,12 @@ class CKEditorAdminTest extends WebTestBase {
$select = $this->xpath('//select[@name="editor[editor]"]');
$select_is_disabled = $this->xpath('//select[@name="editor[editor]" and @disabled="disabled"]');
$options = $this->xpath('//select[@name="editor[editor]"]/option');
$this->assertTrue(count($select) === 1, 'The Text Editor select exists.');
$this->assertTrue(count($select_is_disabled) === 0, 'The Text Editor select is not disabled.');
$this->assertTrue(count($options) === 2, 'The Text Editor select has two options.');
$this->assertTrue(((string) $options[0]) === 'None', 'Option 1 in the Text Editor select is "None".');
$this->assertTrue(((string) $options[1]) === 'CKEditor', 'Option 2 in the Text Editor select is "CKEditor".');
$this->assertTrue(((string) $options[0]['selected']) === 'selected', 'Option 1 ("None") is selected.');
$this->assertCount(1, $select, 'The Text Editor select exists.');
$this->assertCount(0, $select_is_disabled, 'The Text Editor select is not disabled.');
$this->assertCount(2, $options, 'The Text Editor select has two options.');
$this->assertSame('None', $options[0]->getText(), 'Option 1 in the Text Editor select is "None".');
$this->assertSame('CKEditor', $options[1]->getText(), 'Option 2 in the Text Editor select is "CKEditor".');
$this->assertSame('selected', $options[0]->getAttribute('selected'), 'Option 1 ("None") is selected.');
// Name our fancy new text format, select the "CKEditor" editor and click
// the "Configure" button.
@ -244,7 +246,7 @@ class CKEditorAdminTest extends WebTestBase {
'format' => 'amazing_format',
'editor[editor]' => 'ckeditor',
];
$this->drupalPostAjaxForm(NULL, $edit, 'editor_configure');
$this->drupalPostForm(NULL, $edit, 'editor_configure');
$filter_format = FilterFormat::load('amazing_format');
$this->assertFalse($filter_format, 'No FilterFormat config entity exists yet.');
$editor = Editor::load('amazing_format');
@ -258,7 +260,9 @@ class CKEditorAdminTest extends WebTestBase {
$this->assertFieldByName('editor[settings][toolbar][button_groups]', $expected_buttons_value);
// Regression test for https://www.drupal.org/node/2606460.
$this->assertTrue(strpos($this->drupalSettings['ckeditor']['toolbarAdmin'], '<li data-drupal-ckeditor-button-name="Bold" class="ckeditor-button"><a href="#" class="cke-icon-only cke_ltr" role="button" title="bold" aria-label="bold"><span class="cke_button_icon cke_button__bold_icon">bold</span></a></li>') !== FALSE);
$settings = $this->getDrupalSettings();
$expected = $settings['ckeditor']['toolbarAdmin'];
$this->assertTrue(strpos($expected, '<li data-drupal-ckeditor-button-name="Bold" class="ckeditor-button"><a href="#" class="cke-icon-only cke_ltr" role="button" title="bold" aria-label="bold"><span class="cke_button_icon cke_button__bold_icon">bold</span></a></li>') !== FALSE);
// Ensure the styles textarea exists and is initialized empty.
$styles_textarea = $this->xpath('//textarea[@name="editor[settings][plugins][stylescombo][styles]"]');

View file

@ -1,17 +1,17 @@
<?php
namespace Drupal\ckeditor\Tests;
namespace Drupal\Tests\ckeditor\Functional;
use Drupal\editor\Entity\Editor;
use Drupal\simpletest\WebTestBase;
use Drupal\filter\Entity\FilterFormat;
use Drupal\Tests\BrowserTestBase;
/**
* Tests loading of CKEditor.
*
* @group ckeditor
*/
class CKEditorLoadingTest extends WebTestBase {
class CKEditorLoadingTest extends BrowserTestBase {
/**
* Modules to enable.

View file

@ -1,17 +1,17 @@
<?php
namespace Drupal\ckeditor\Tests;
namespace Drupal\Tests\ckeditor\Functional;
use Drupal\editor\Entity\Editor;
use Drupal\simpletest\WebTestBase;
use Drupal\filter\Entity\FilterFormat;
use Drupal\Tests\BrowserTestBase;
/**
* Tests administration of the CKEditor StylesCombo plugin.
*
* @group ckeditor
*/
class CKEditorStylesComboAdminTest extends WebTestBase {
class CKEditorStylesComboAdminTest extends BrowserTestBase {
/**
* Modules to enable.

View file

@ -11,7 +11,7 @@ process:
# the cid field to allow incremental migrations.
cid: cid
pid:
plugin: migration
plugin: migration_lookup
migration: d6_comment
source: pid
entity_id: nid
@ -32,7 +32,7 @@ process:
thread: thread
'comment_body/value': comment
'comment_body/format':
plugin: migration
plugin: migration_lookup
migration: d6_filter_format
source: format
destination:

View file

@ -11,7 +11,7 @@ process:
# the cid field to allow incremental migrations.
cid: cid
pid:
plugin: migration
plugin: migration_lookup
migration: d7_comment
source: pid
entity_id: nid

View file

@ -10,7 +10,7 @@ use Drupal\Core\Session\AnonymousUserSession;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Entity\FieldConfig;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\simpletest\TestBase;
use Drupal\Tests\Traits\Core\GeneratePermutationsTrait;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
@ -23,6 +23,7 @@ use Drupal\user\RoleInterface;
class CommentFieldAccessTest extends EntityKernelTestBase {
use CommentTestTrait;
use GeneratePermutationsTrait;
/**
* Modules to install.
@ -203,7 +204,7 @@ class CommentFieldAccessTest extends EntityKernelTestBase {
'comment' => [$comment1, $comment2, $comment3, $comment4],
'user' => [$comment_admin_user, $comment_enabled_user, $comment_no_edit_user, $comment_disabled_user, $anonymous_user]
];
$permutations = TestBase::generatePermutations($combinations);
$permutations = $this->generatePermutations($combinations);
// Check access to administrative fields.
foreach ($this->administrativeFields as $field) {

View file

@ -17,7 +17,7 @@ class MigrateCommentTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['comment'];
public static $modules = ['comment', 'menu_ui'];
/**
* {@inheritdoc}

View file

@ -11,6 +11,11 @@ use Drupal\Core\Entity\Entity\EntityViewDisplay;
*/
class MigrateCommentVariableEntityDisplayTest extends MigrateCommentVariableDisplayBase {
/**
* {@inheritdoc}
*/
public static $modules = ['menu_ui'];
/**
* {@inheritdoc}
*/

View file

@ -11,6 +11,11 @@ use Drupal\Core\Entity\Entity\EntityFormDisplay;
*/
class MigrateCommentVariableEntityFormDisplayTest extends MigrateCommentVariableDisplayBase {
/**
* {@inheritdoc}
*/
public static $modules = ['menu_ui'];
/**
* {@inheritdoc}
*/

View file

@ -15,7 +15,7 @@ class MigrateCommentVariableFieldTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['comment'];
public static $modules = ['comment', 'menu_ui'];
/**
* {@inheritdoc}

View file

@ -16,7 +16,7 @@ class MigrateCommentVariableInstanceTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['comment'];
public static $modules = ['comment', 'menu_ui'];
/**
* {@inheritdoc}

View file

@ -12,7 +12,7 @@ use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
*/
class MigrateCommentEntityDisplayTest extends MigrateDrupal7TestBase {
public static $modules = ['node', 'comment', 'text'];
public static $modules = ['node', 'comment', 'text', 'menu_ui'];
/**
* {@inheritdoc}

View file

@ -12,7 +12,7 @@ use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
*/
class MigrateCommentEntityFormDisplaySubjectTest extends MigrateDrupal7TestBase {
public static $modules = ['node', 'comment', 'text'];
public static $modules = ['node', 'comment', 'text', 'menu_ui'];
/**
* {@inheritdoc}

View file

@ -12,7 +12,7 @@ use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
*/
class MigrateCommentEntityFormDisplayTest extends MigrateDrupal7TestBase {
public static $modules = ['node', 'comment', 'text'];
public static $modules = ['node', 'comment', 'text', 'menu_ui'];
/**
* {@inheritdoc}

View file

@ -15,7 +15,7 @@ use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
*/
class MigrateCommentFieldInstanceTest extends MigrateDrupal7TestBase {
public static $modules = ['node', 'comment', 'text'];
public static $modules = ['node', 'comment', 'text', 'menu_ui'];
/**
* {@inheritdoc}

View file

@ -14,7 +14,7 @@ use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
*/
class MigrateCommentFieldTest extends MigrateDrupal7TestBase {
public static $modules = ['node', 'comment', 'text'];
public static $modules = ['node', 'comment', 'text', 'menu_ui'];
/**
* {@inheritdoc}

View file

@ -14,7 +14,7 @@ use Drupal\node\NodeInterface;
*/
class MigrateCommentTest extends MigrateDrupal7TestBase {
public static $modules = ['filter', 'node', 'comment', 'text'];
public static $modules = ['filter', 'node', 'comment', 'text', 'menu_ui'];
/**
* {@inheritdoc}

View file

@ -13,7 +13,7 @@ use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
*/
class MigrateCommentTypeTest extends MigrateDrupal7TestBase {
public static $modules = ['node', 'comment', 'text'];
public static $modules = ['node', 'comment', 'text', 'menu_ui'];
/**
* {@inheritdoc}

View file

@ -0,0 +1,106 @@
<?php
namespace Drupal\Tests\comment\Kernel\Plugin\migrate\source\d6;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests the Drupal 6 comment source w/ high water handling.
*
* @covers \Drupal\comment\Plugin\migrate\source\d6\Comment
*
* @group comment
*/
class CommentSourceWithHighWaterTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['comment', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['comments'] = [
[
'cid' => 1,
'pid' => 0,
'nid' => 2,
'uid' => 3,
'subject' => 'subject value 1',
'comment' => 'comment value 1',
'hostname' => 'hostname value 1',
'timestamp' => 1382255613,
'status' => 0,
'thread' => '',
'name' => '',
'mail' => '',
'homepage' => '',
'format' => 'testformat1',
'type' => 'story',
],
[
'cid' => 2,
'pid' => 1,
'nid' => 3,
'uid' => 4,
'subject' => 'subject value 2',
'comment' => 'comment value 2',
'hostname' => 'hostname value 2',
'timestamp' => 1382255662,
'status' => 0,
'thread' => '',
'name' => '',
'mail' => '',
'homepage' => '',
'format' => 'testformat2',
'type' => 'page',
],
];
$tests[0]['source_data']['node'] = [
[
'nid' => 2,
'type' => 'story',
],
[
'nid' => 3,
'type' => 'page',
],
];
// The expected results.
$tests[0]['expected_data'] = [
[
'cid' => 2,
'pid' => 1,
'nid' => 3,
'uid' => 4,
'subject' => 'subject value 2',
'comment' => 'comment value 2',
'hostname' => 'hostname value 2',
'timestamp' => 1382255662,
'status' => 1,
'thread' => '',
'name' => '',
'mail' => '',
'homepage' => '',
'format' => 'testformat2',
'type' => 'page',
],
];
// The expected count is the count returned by the query before the query
// is modified by SqlBase::initializeIterator().
$tests[0]['expected_count'] = 2;
$tests[0]['configuration']['high_water_property']['name'] = 'timestamp';
$tests[0]['high_water'] = $tests[0]['source_data']['comments'][0]['timestamp'];
return $tests;
}
}

View file

@ -0,0 +1,116 @@
<?php
namespace Drupal\Tests\comment\Kernel\Plugin\migrate\source\d6;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests D6 comment source plugin.
*
* @covers \Drupal\comment\Plugin\migrate\source\d6\Comment
* @group comment
*/
class CommentTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['comment', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['comments'] = [
[
'cid' => 1,
'pid' => 0,
'nid' => 2,
'uid' => 3,
'subject' => 'subject value 1',
'comment' => 'comment value 1',
'hostname' => 'hostname value 1',
'timestamp' => 1382255613,
'status' => 0,
'thread' => '',
'name' => '',
'mail' => '',
'homepage' => '',
'format' => 'testformat1',
'type' => 'story',
],
[
'cid' => 2,
'pid' => 1,
'nid' => 3,
'uid' => 4,
'subject' => 'subject value 2',
'comment' => 'comment value 2',
'hostname' => 'hostname value 2',
'timestamp' => 1382255662,
'status' => 0,
'thread' => '',
'name' => '',
'mail' => '',
'homepage' => '',
'format' => 'testformat2',
'type' => 'page',
],
];
$tests[0]['source_data']['node'] = [
[
'nid' => 2,
'type' => 'story',
],
[
'nid' => 3,
'type' => 'page',
],
];
// The expected results.
$tests[0]['expected_data'] = [
[
'cid' => 1,
'pid' => 0,
'nid' => 2,
'uid' => 3,
'subject' => 'subject value 1',
'comment' => 'comment value 1',
'hostname' => 'hostname value 1',
'timestamp' => 1382255613,
'status' => 1,
'thread' => '',
'name' => '',
'mail' => '',
'homepage' => '',
'format' => 'testformat1',
'type' => 'story',
],
[
'cid' => 2,
'pid' => 1,
'nid' => 3,
'uid' => 4,
'subject' => 'subject value 2',
'comment' => 'comment value 2',
'hostname' => 'hostname value 2',
'timestamp' => 1382255662,
'status' => 1,
'thread' => '',
'name' => '',
'mail' => '',
'homepage' => '',
'format' => 'testformat2',
'type' => 'page',
],
];
return $tests;
}
}

View file

@ -0,0 +1,62 @@
<?php
namespace Drupal\Tests\comment\Kernel\Plugin\migrate\source\d6;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests comment variable per comment type source plugin.
*
* @covers \Drupal\comment\Plugin\migrate\source\d6\CommentVariablePerCommentType
* @group comment
*/
class CommentVariablePerCommentTypeTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['comment', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['node_type'] = [
[
'type' => 'page',
],
[
'type' => 'story',
],
];
$tests[0]['source_data']['variable'] = [
[
'name' => 'comment_subject_field_page',
'value' => serialize(1),
],
[
'name' => 'comment_subject_field_story',
'value' => serialize(0),
],
];
// The expected results.
// Each result will also include a label and description, but those are
// static values set by the source plugin and don't need to be asserted.
$tests[0]['expected_data'] = [
[
'comment_type' => 'comment',
],
[
'comment_type' => 'comment_no_subject',
],
];
return $tests;
}
}

View file

@ -0,0 +1,92 @@
<?php
namespace Drupal\Tests\comment\Kernel\Plugin\migrate\source\d6;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests d6_comment_variable source plugin.
*
* @covers \Drupal\comment\Plugin\migrate\source\d6\CommentVariable
* @group comment
*/
class CommentVariableTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['comment', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['node_type'] = [
[
'type' => 'page',
],
];
$tests[0]['source_data']['variable'] = [
[
'name' => 'comment_page',
'value' => serialize(1),
],
[
'name' => 'comment_default_mode_page',
'value' => serialize(1),
],
[
'name' => 'comment_default_order_page',
'value' => serialize(1),
],
[
'name' => 'comment_default_per_page_page',
'value' => serialize(50),
],
[
'name' => 'comment_controls_page',
'value' => serialize(1),
],
[
'name' => 'comment_anonymous_page',
'value' => serialize(1),
],
[
'name' => 'comment_subject_field_page',
'value' => serialize(1),
],
[
'name' => 'comment_preview_page',
'value' => serialize(1),
],
[
'name' => 'comment_form_location_page',
'value' => serialize(1),
],
];
// The expected results.
$tests[0]['expected_data'] = [
[
'comment' => '1',
'comment_default_mode' => '1',
'comment_default_order' => '1',
'comment_default_per_page' => '50',
'comment_controls' => '1',
'comment_anonymous' => '1',
'comment_subject_field' => '1',
'comment_preview' => '1',
'comment_form_location' => '1',
'node_type' => 'page',
'comment_type' => 'comment',
],
];
return $tests;
}
}

View file

@ -0,0 +1,117 @@
<?php
namespace Drupal\Tests\comment\Kernel\Plugin\migrate\source\d7;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests D7 comment source plugin.
*
* @covers \Drupal\comment\Plugin\migrate\source\d7\Comment
* @group comment
*/
class CommentTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['comment', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['comment'] = [
[
'cid' => '1',
'pid' => '0',
'nid' => '1',
'uid' => '1',
'subject' => 'A comment',
'hostname' => '::1',
'created' => '1421727536',
'changed' => '1421727536',
'status' => '1',
'thread' => '01/',
'name' => 'admin',
'mail' => '',
'homepage' => '',
'language' => 'und',
],
];
$tests[0]['source_data']['node'] = [
[
'nid' => '1',
'vid' => '1',
'type' => 'test_content_type',
'language' => 'en',
'title' => 'A Node',
'uid' => '1',
'status' => '1',
'created' => '1421727515',
'changed' => '1421727515',
'comment' => '2',
'promote' => '1',
'sticky' => '0',
'tnid' => '0',
'translate' => '0',
],
];
$tests[0]['source_data']['field_config_instance'] = [
[
'id' => '14',
'field_id' => '1',
'field_name' => 'comment_body',
'entity_type' => 'comment',
'bundle' => 'comment_node_test_content_type',
'data' => 'a:0:{}',
'deleted' => '0',
],
];
$tests[0]['source_data']['field_data_comment_body'] = [
[
'entity_type' => 'comment',
'bundle' => 'comment_node_test_content_type',
'deleted' => '0',
'entity_id' => '1',
'revision_id' => '1',
'language' => 'und',
'delta' => '0',
'comment_body_value' => 'This is a comment',
'comment_body_format' => 'filtered_html',
],
];
// The expected results.
$tests[0]['expected_data'] = [
[
'cid' => '1',
'pid' => '0',
'nid' => '1',
'uid' => '1',
'subject' => 'A comment',
'hostname' => '::1',
'created' => '1421727536',
'changed' => '1421727536',
'status' => '1',
'thread' => '01/',
'name' => 'admin',
'mail' => '',
'homepage' => '',
'language' => 'und',
'comment_body' => [
[
'value' => 'This is a comment',
'format' => 'filtered_html',
],
],
],
];
return $tests;
}
}

View file

@ -1,44 +1,30 @@
<?php
namespace Drupal\Tests\comment\Unit\Migrate\d7;
namespace Drupal\Tests\comment\Kernel\Plugin\migrate\source\d7;
use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests D7 comment type source plugin.
*
* @covers \Drupal\comment\Plugin\migrate\source\d7\CommentType
* @group comment
*/
class CommentTypeTest extends MigrateSqlSourceTestCase {
const PLUGIN_CLASS = 'Drupal\comment\Plugin\migrate\source\d7\CommentType';
protected $migrationConfiguration = [
'id' => 'test',
'source' => [
'plugin' => 'd7_comment_type',
],
];
protected $expectedResults = [
[
'bundle' => 'comment_node_article',
'node_type' => 'article',
'default_mode' => '1',
'per_page' => '50',
'anonymous' => '0',
'form_location' => '1',
'preview' => '0',
'subject' => '1',
'label' => 'Article comment',
],
];
class CommentTypeTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->databaseContents['node_type'] = [
public static $modules = ['comment', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['node_type'] = [
[
'type' => 'article',
'name' => 'Article',
@ -55,7 +41,7 @@ class CommentTypeTest extends MigrateSqlSourceTestCase {
'orig_type' => 'article',
],
];
$this->databaseContents['field_config_instance'] = [
$tests[0]['source_data']['field_config_instance'] = [
[
'id' => '14',
'field_id' => '1',
@ -66,7 +52,7 @@ class CommentTypeTest extends MigrateSqlSourceTestCase {
'deleted' => '0',
],
];
$this->databaseContents['variable'] = [
$tests[0]['source_data']['variable'] = [
[
'name' => 'comment_default_mode_article',
'value' => serialize(1),
@ -92,7 +78,22 @@ class CommentTypeTest extends MigrateSqlSourceTestCase {
'value' => serialize(1),
],
];
parent::setUp();
// The expected results.
$tests[0]['expected_data'] = [
[
'bundle' => 'comment_node_article',
'node_type' => 'article',
'default_mode' => '1',
'per_page' => '50',
'anonymous' => '0',
'form_location' => '1',
'preview' => '0',
'subject' => '1',
'label' => 'Article comment',
],
];
return $tests;
}
}

View file

@ -6,7 +6,7 @@ use Drupal\comment\CommentLinkBuilder;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\Core\Url;
use Drupal\node\NodeInterface;
use Drupal\simpletest\TestBase;
use Drupal\Tests\Traits\Core\GeneratePermutationsTrait;
use Drupal\Tests\UnitTestCase;
/**
@ -15,6 +15,8 @@ use Drupal\Tests\UnitTestCase;
*/
class CommentLinkBuilderTest extends UnitTestCase {
use GeneratePermutationsTrait;
/**
* Comment manager mock.
*
@ -195,7 +197,7 @@ class CommentLinkBuilderTest extends UnitTestCase {
'teaser', 'rss', 'full',
],
];
$permutations = TestBase::generatePermutations($combinations);
$permutations = $this->generatePermutations($combinations);
foreach ($permutations as $combination) {
$case = [
$this->getMockNode(TRUE, $combination['comments'], $combination['form_location'], $combination['comment_count']),

View file

@ -1,23 +0,0 @@
<?php
namespace Drupal\Tests\comment\Unit\Migrate\d6;
/**
* Tests the Drupal 6 comment source w/ high water handling.
*
* @group comment
*/
class CommentSourceWithHighWaterTest extends CommentTestBase {
const ORIGINAL_HIGH_WATER = 1382255613;
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->migrationConfiguration['source']['high_water_property']['name'] = 'timestamp';
array_shift($this->expectedResults);
parent::setUp();
}
}

View file

@ -1,12 +0,0 @@
<?php
namespace Drupal\Tests\comment\Unit\Migrate\d6;
/**
* Tests D6 comment source plugin.
*
* @group comment
*/
class CommentTest extends CommentTestBase {
}

View file

@ -1,80 +0,0 @@
<?php
namespace Drupal\Tests\comment\Unit\Migrate\d6;
use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
/**
* Base class for comment source unit tests.
*/
abstract class CommentTestBase extends MigrateSqlSourceTestCase {
// The plugin system is not working during unit testing so the source plugin
// class needs to be manually specified.
const PLUGIN_CLASS = 'Drupal\comment\Plugin\migrate\source\d6\Comment';
// The fake Migration configuration entity.
protected $migrationConfiguration = [
// The ID of the entity, can be any string.
'id' => 'test',
// This needs to be the identifier of the actual key: cid for comment, nid
// for node and so on.
'source' => [
'plugin' => 'd6_comment',
],
];
// We need to set up the database contents; it's easier to do that below.
protected $expectedResults = [
[
'cid' => 1,
'pid' => 0,
'nid' => 2,
'uid' => 3,
'subject' => 'subject value 1',
'comment' => 'comment value 1',
'hostname' => 'hostname value 1',
'timestamp' => 1382255613,
'status' => 1,
'thread' => '',
'name' => '',
'mail' => '',
'homepage' => '',
'format' => 'testformat1',
'type' => 'story',
],
[
'cid' => 2,
'pid' => 1,
'nid' => 3,
'uid' => 4,
'subject' => 'subject value 2',
'comment' => 'comment value 2',
'hostname' => 'hostname value 2',
'timestamp' => 1382255662,
'status' => 1,
'thread' => '',
'name' => '',
'mail' => '',
'homepage' => '',
'format' => 'testformat2',
'type' => 'page',
],
];
/**
* {@inheritdoc}
*/
protected function setUp() {
foreach ($this->expectedResults as $k => $row) {
$this->databaseContents['comments'][$k] = $row;
$this->databaseContents['comments'][$k]['status'] = 1 - $this->databaseContents['comments'][$k]['status'];
}
// Add node table data.
$this->databaseContents['node'][] = ['nid' => 2, 'type' => 'story'];
$this->databaseContents['node'][] = ['nid' => 3, 'type' => 'page'];
parent::setUp();
}
}

View file

@ -1,59 +0,0 @@
<?php
namespace Drupal\Tests\comment\Unit\Migrate\d6;
use Drupal\comment\Plugin\migrate\source\d6\CommentVariablePerCommentType;
use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
/**
* @coversDefaultClass \Drupal\comment\Plugin\migrate\source\d6\CommentVariablePerCommentType
* @group comment
*/
class CommentVariablePerCommentTypeTest extends MigrateSqlSourceTestCase {
const PLUGIN_CLASS = CommentVariablePerCommentType::class;
protected $migrationConfiguration = [
'id' => 'test',
'source' => [
'plugin' => 'd6_comment_variable_per_comment_type',
],
];
protected $expectedResults = [
// Each result will also include a label and description, but those are
// static values set by the source plugin and don't need to be asserted.
[
'comment_type' => 'comment',
],
[
'comment_type' => 'comment_no_subject',
],
];
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->databaseContents['node_type'] = [
[
'type' => 'page',
],
[
'type' => 'story',
],
];
$this->databaseContents['variable'] = [
[
'name' => 'comment_subject_field_page',
'value' => serialize(1),
],
[
'name' => 'comment_subject_field_story',
'value' => serialize(0),
],
];
parent::setUp();
}
}

View file

@ -1,89 +0,0 @@
<?php
namespace Drupal\Tests\comment\Unit\Migrate\d6;
use Drupal\comment\Plugin\migrate\source\d6\CommentVariable;
use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
/**
* @coversDefaultClass \Drupal\comment\Plugin\migrate\source\d6\CommentVariable
* @group comment
*/
class CommentVariableTest extends MigrateSqlSourceTestCase {
const PLUGIN_CLASS = CommentVariable::class;
protected $migrationConfiguration = [
'id' => 'test',
'source' => [
'plugin' => 'd6_comment_variable',
],
];
protected $expectedResults = [
[
'comment' => '1',
'comment_default_mode' => '1',
'comment_default_order' => '1',
'comment_default_per_page' => '50',
'comment_controls' => '1',
'comment_anonymous' => '1',
'comment_subject_field' => '1',
'comment_preview' => '1',
'comment_form_location' => '1',
'node_type' => 'page',
'comment_type' => 'comment',
],
];
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->databaseContents['node_type'] = [
[
'type' => 'page',
],
];
$this->databaseContents['variable'] = [
[
'name' => 'comment_page',
'value' => serialize(1),
],
[
'name' => 'comment_default_mode_page',
'value' => serialize(1),
],
[
'name' => 'comment_default_order_page',
'value' => serialize(1),
],
[
'name' => 'comment_default_per_page_page',
'value' => serialize(50),
],
[
'name' => 'comment_controls_page',
'value' => serialize(1),
],
[
'name' => 'comment_anonymous_page',
'value' => serialize(1),
],
[
'name' => 'comment_subject_field_page',
'value' => serialize(1),
],
[
'name' => 'comment_preview_page',
'value' => serialize(1),
],
[
'name' => 'comment_form_location_page',
'value' => serialize(1),
],
];
parent::setUp();
}
}

View file

@ -1,100 +0,0 @@
<?php
namespace Drupal\Tests\comment\Unit\Migrate\d7;
use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
/**
* Tests D7 comment source plugin.
*
* @group comment
*/
class CommentTest extends MigrateSqlSourceTestCase {
const PLUGIN_CLASS = 'Drupal\comment\Plugin\migrate\source\d7\Comment';
protected $migrationConfiguration = [
'id' => 'test',
'source' => [
'plugin' => 'd7_comment',
],
];
protected $expectedResults = [
[
'cid' => '1',
'pid' => '0',
'nid' => '1',
'uid' => '1',
'subject' => 'A comment',
'hostname' => '::1',
'created' => '1421727536',
'changed' => '1421727536',
'status' => '1',
'thread' => '01/',
'name' => 'admin',
'mail' => '',
'homepage' => '',
'language' => 'und',
'comment_body' => [
[
'value' => 'This is a comment',
'format' => 'filtered_html',
],
],
],
];
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->databaseContents['comment'] = $this->expectedResults;
unset($this->databaseContents['comment'][0]['comment_body']);
$this->databaseContents['node'] = [
[
'nid' => '1',
'vid' => '1',
'type' => 'test_content_type',
'language' => 'en',
'title' => 'A Node',
'uid' => '1',
'status' => '1',
'created' => '1421727515',
'changed' => '1421727515',
'comment' => '2',
'promote' => '1',
'sticky' => '0',
'tnid' => '0',
'translate' => '0',
],
];
$this->databaseContents['field_config_instance'] = [
[
'id' => '14',
'field_id' => '1',
'field_name' => 'comment_body',
'entity_type' => 'comment',
'bundle' => 'comment_node_test_content_type',
'data' => 'a:0:{}',
'deleted' => '0',
],
];
$this->databaseContents['field_data_comment_body'] = [
[
'entity_type' => 'comment',
'bundle' => 'comment_node_test_content_type',
'deleted' => '0',
'entity_id' => '1',
'revision_id' => '1',
'language' => 'und',
'delta' => '0',
'comment_body_value' => 'This is a comment',
'comment_body_format' => 'filtered_html',
],
];
parent::setUp();
}
}

View file

@ -155,6 +155,29 @@ EOD;
];
$this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import'));
$this->assertRaw(t('Configuration %name depends on the %owner module that will not be installed after import.', ['%name' => 'config_test.dynamic.second', '%owner' => 'does_not_exist']));
// Try to preform an update which would create a PHP object if Yaml parsing
// not securely set up.
// Perform an update.
$import = <<<EOD
id: second
uuid: $second_uuid
label: !php/object "O:36:\"Drupal\\\Core\\\Test\\\ObjectSerialization\":0:{}"
weight: 0
style: ''
status: '0'
EOD;
$edit = [
'config_type' => 'config_test',
'import' => $import,
];
$this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import'));
$this->assertRaw(t('Are you sure you want to update the %name @type?', ['%name' => 'second', '@type' => 'test configuration']));
$this->drupalPostForm(NULL, [], t('Confirm'));
$entity = $storage->load('second');
$this->assertRaw(t('The configuration was imported successfully.'));
$this->assertTrue(is_string($entity->label()), 'Entity label is a string');
$this->assertTrue(strpos($entity->label(), 'ObjectSerialization') > 0, 'Label contains serialized object');
}
/**

View file

@ -0,0 +1,13 @@
id: other_module_test_with_dependency
label: 'Other module test with dependency'
weight: 0
style: ''
status: true
langcode: en
protected_property: Default
dependencies:
enforced:
module:
- config_other_module_config_test
config:
- config_test.dynamic.dotted.english

View file

@ -0,0 +1,13 @@
id: yet_another_module_test_with_dependency
label: 'Yet anther module test with dependency'
weight: 0
style: ''
status: true
langcode: en
protected_property: Default
dependencies:
enforced:
module:
- config_other_module_config_test
config:
- config_test.dynamic.dotted.english

View file

@ -0,0 +1,5 @@
name: 'Config install double dependency test'
type: module
package: Testing
version: VERSION
core: 8.x

View file

@ -197,7 +197,7 @@ abstract class ConfigTranslationFormBase extends FormBase implements BaseFormIdI
public function submitForm(array &$form, FormStateInterface $form_state) {
$form_values = $form_state->getValue(['translation', 'config_names']);
foreach ($this->mapper->getConfigNames() as $name) {
foreach ($form_values as $name => $value) {
$schema = $this->typedConfigManager->get($name);
// Set configuration values based on form submission and source values.
@ -205,7 +205,7 @@ abstract class ConfigTranslationFormBase extends FormBase implements BaseFormIdI
$config_translation = $this->languageManager->getLanguageConfigOverride($this->language->getId(), $name);
$element = $this->createFormElement($schema);
$element->setConfig($base_config, $config_translation, $form_values[$name]);
$element->setConfig($base_config, $config_translation, $value);
// If no overrides, delete language specific configuration file.
$saved_config = $config_translation->get();

View file

@ -12,6 +12,7 @@ use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\filter\Entity\FilterFormat;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\node\Entity\NodeType;
use Drupal\simpletest\WebTestBase;
/**
@ -1033,6 +1034,45 @@ class ConfigTranslationUiTest extends WebTestBase {
$this->assertEqual($expected, $actual);
}
/**
* Tests field translation for node fields.
*/
public function testNodeFieldTranslation() {
NodeType::create(['type' => 'article', 'name' => 'Article'])->save();
$field_name = 'translatable_field';
$field_storage = FieldStorageConfig::create([
'field_name' => $field_name,
'entity_type' => 'node',
'type' => 'text',
]);
$field_storage->setSetting('translatable_storage_setting', 'translatable_storage_setting');
$field_storage->save();
$field = FieldConfig::create([
'field_name' => $field_name,
'entity_type' => 'node',
'bundle' => 'article',
]);
$field->save();
$this->drupalLogin($this->translatorUser);
$this->drupalGet("/entity_test/structure/article/fields/node.article.$field_name/translate");
$this->clickLink('Add');
$form_values = [
'translation[config_names][field.field.node.article.translatable_field][description]' => 'FR Help text.',
'translation[config_names][field.field.node.article.translatable_field][label]' => 'FR label',
];
$this->drupalPostForm(NULL, $form_values, 'Save translation');
$this->assertText('Successfully saved French translation.');
// Check that the translations are saved.
$this->clickLink('Add');
$this->assertRaw('FR label');
}
/**
* Gets translation from locale storage.
*

View file

@ -1,6 +1,6 @@
<?php
namespace Drupal\Tests\config_translation\Functional;
namespace Drupal\config_translation\Tests;
use Drupal\views_ui\Tests\UITestBase;

View file

@ -11,7 +11,7 @@ process:
plugin: machine_name
source: category
-
plugin: dedupe_entity
plugin: make_unique_entity_field
entity_type: contact_form
field: id
length: 32

View file

@ -11,7 +11,7 @@ process:
user_default_enabled: contact_default_status
'flood/limit': contact_hourly_threshold
default_form:
plugin: migration
plugin: migration_lookup
migration: contact_category
source: default_category
destination:

View file

@ -11,7 +11,7 @@ process:
user_default_enabled: contact_default_status
'flood/limit': contact_threshold_limit
default_form:
plugin: migration
plugin: migration_lookup
migration: contact_category
source: default_category
destination:

View file

@ -1,5 +1,7 @@
entity-moderation-form:
content_moderation:
version: VERSION
css:
layout:
css/entity-moderation-form.css: {}
component:
css/content_moderation.module.css: {}
theme:
css/content_moderation.theme.css: {}

View file

@ -10,7 +10,6 @@ use Drupal\content_moderation\EntityTypeInfo;
use Drupal\content_moderation\ContentPreprocess;
use Drupal\content_moderation\Plugin\Action\ModerationOptOutPublishNode;
use Drupal\content_moderation\Plugin\Action\ModerationOptOutUnpublishNode;
use Drupal\content_moderation\Plugin\Menu\EditTab;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
@ -99,22 +98,6 @@ function content_moderation_entity_update(EntityInterface $entity) {
->entityUpdate($entity);
}
/**
* Implements hook_local_tasks_alter().
*/
function content_moderation_local_tasks_alter(&$local_tasks) {
$content_entity_type_ids = array_keys(array_filter(\Drupal::entityTypeManager()->getDefinitions(), function (EntityTypeInterface $entity_type) {
return $entity_type->isRevisionable();
}));
foreach ($content_entity_type_ids as $content_entity_type_id) {
if (isset($local_tasks["entity.$content_entity_type_id.edit_form"])) {
$local_tasks["entity.$content_entity_type_id.edit_form"]['class'] = EditTab::class;
$local_tasks["entity.$content_entity_type_id.edit_form"]['entity_type_id'] = $content_entity_type_id;
}
}
}
/**
* Implements hook_form_alter().
*/

View file

@ -2,7 +2,7 @@ view any unpublished content:
title: 'View any unpublished content'
description: 'This permission is necessary for any users that may moderate content.'
'view content moderation':
view content moderation:
title: 'View content moderation'
description: 'View content moderation.'

View file

@ -1,14 +1,17 @@
/**
* @file
* Component styles for the content_moderation module.
*/
ul.entity-moderation-form {
list-style: none;
display: -webkit-flex; /* Safari */
display: flex;
-webkit-flex-wrap: wrap; /* Safari */
flex-wrap: wrap;
flex-wrap: wrap;
-webkit-justify-content: space-around; /* Safari */
justify-content: space-around;
justify-content: space-around;
-webkit-align-items: flex-end; /* Safari */
align-items: flex-end;
border-bottom: 1px solid gray;
align-items: flex-end;
}
ul.entity-moderation-form input[type=submit] {

View file

@ -0,0 +1,7 @@
/**
* @file
* Theme styles for the content_moderation module.
*/
ul.entity-moderation-form {
border-bottom: 1px solid gray;
}

View file

@ -7,6 +7,8 @@ use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\content_moderation\ModerationInformationInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\user\EntityOwnerInterface;
use Symfony\Component\Routing\Route;
/**
@ -41,18 +43,31 @@ class LatestRevisionCheck implements AccessInterface {
* The route to check against.
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The parametrized route.
* @param \Drupal\Core\Session\AccountInterface $account
* The current user account.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*
* @see \Drupal\Core\Entity\EntityAccessCheck
*/
public function access(Route $route, RouteMatchInterface $route_match) {
public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account) {
// This tab should not show up unless there's a reason to show it.
$entity = $this->loadEntity($route, $route_match);
return $this->moderationInfo->hasForwardRevision($entity)
? AccessResult::allowed()->addCacheableDependency($entity)
: AccessResult::forbidden()->addCacheableDependency($entity);
if ($this->moderationInfo->hasForwardRevision($entity)) {
// Check the global permissions first.
$access_result = AccessResult::allowedIfHasPermissions($account, ['view latest version', 'view any unpublished content']);
if (!$access_result->isAllowed()) {
// Check entity owner access.
$owner_access = AccessResult::allowedIfHasPermissions($account, ['view latest version', 'view own unpublished content']);
$owner_access = $owner_access->andIf((AccessResult::allowedIf($entity instanceof EntityOwnerInterface && ($entity->getOwnerId() == $account->id()))));
$access_result = $access_result->orIf($owner_access);
}
return $access_result->addCacheableDependency($entity);
}
return AccessResult::forbidden()->addCacheableDependency($entity);
}
/**

View file

@ -0,0 +1,37 @@
<?php
namespace Drupal\content_moderation;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Entity\EntityAccessControlHandler;
use Drupal\Core\Entity\EntityInterface;
/**
* The access control handler for the content_moderation_state entity type.
*
* @see \Drupal\content_moderation\Entity\ContentModerationState
*/
class ContentModerationStateAccessControlHandler extends EntityAccessControlHandler {
/**
* {@inheritdoc}
*/
public function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
// ContentModerationState is an internal entity type. Access is denied for
// viewing, updating, and deleting. In order to update an entity's
// moderation state use its moderation_state field.
return AccessResult::forbidden('ContentModerationState is an internal entity type.');
}
/**
* {@inheritdoc}
*/
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
// ContentModerationState is an internal entity type. Access is denied for
// creating. In order to update an entity's moderation state use its
// moderation_state field.
return AccessResult::forbidden('ContentModerationState is an internal entity type.');
}
}

View file

@ -16,11 +16,20 @@ class ContentModerationStateStorageSchema extends SqlContentEntityStorageSchema
protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $reset = FALSE) {
$schema = parent::getEntitySchema($entity_type, $reset);
// Creates an index to ensure that the lookup in
// \Drupal\content_moderation\Plugin\Field\ModerationStateFieldItemList::getModerationState()
// is performant.
$schema['content_moderation_state_field_data']['indexes'] += [
'content_moderation_state__lookup' => ['content_entity_type_id', 'content_entity_id', 'content_entity_revision_id'],
// Creates unique keys to guarantee the integrity of the entity and to make
// the lookup in ModerationStateFieldItemList::getModerationState() fast.
$unique_keys = [
'content_entity_type_id',
'content_entity_id',
'content_entity_revision_id',
'workflow',
'langcode',
];
$schema['content_moderation_state_field_data']['unique keys'] += [
'content_moderation_state__lookup' => $unique_keys,
];
$schema['content_moderation_state_field_revision']['unique keys'] += [
'content_moderation_state__lookup' => $unique_keys,
];
return $schema;

View file

@ -24,6 +24,7 @@ use Drupal\user\UserInterface;
* handlers = {
* "storage_schema" = "Drupal\content_moderation\ContentModerationStateStorageSchema",
* "views_data" = "\Drupal\views\EntityViewsData",
* "access" = "Drupal\content_moderation\ContentModerationStateAccessControlHandler",
* },
* base_table = "content_moderation_state",
* revision_table = "content_moderation_state_revision",
@ -74,6 +75,7 @@ class ContentModerationState extends ContentEntityBase implements ContentModerat
->setLabel(t('Content entity type ID'))
->setDescription(t('The ID of the content entity type this moderation state is for.'))
->setRequired(TRUE)
->setSetting('max_length', EntityTypeInterface::ID_MAX_LENGTH)
->setRevisionable(TRUE);
$fields['content_entity_id'] = BaseFieldDefinition::create('integer')
@ -82,10 +84,6 @@ class ContentModerationState extends ContentEntityBase implements ContentModerat
->setRequired(TRUE)
->setRevisionable(TRUE);
// @todo https://www.drupal.org/node/2779931 Add constraint that enforces
// unique content_entity_type_id, content_entity_id and
// content_entity_revision_id.
$fields['content_entity_revision_id'] = BaseFieldDefinition::create('integer')
->setLabel(t('Content entity revision ID'))
->setDescription(t('The revision ID of the content entity this moderation state is for.'))

View file

@ -105,9 +105,10 @@ class EntityOperations implements ContainerInjectionInterface {
/** @var \Drupal\content_moderation\ContentModerationState $current_state */
$current_state = $workflow->getState($entity->moderation_state->value);
// This entity is default if it is new, the default revision, or the
// default revision is not published.
// This entity is default if it is new, a new translation, the default
// revision, or the default revision is not published.
$update_default_revision = $entity->isNew()
|| $entity->isNewTranslation()
|| $current_state->isDefaultRevisionState()
|| !$this->isDefaultRevisionPublished($entity, $workflow);
@ -157,7 +158,7 @@ class EntityOperations implements ContainerInjectionInterface {
$workflow = $this->moderationInfo->getWorkflowForEntity($entity);
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
if (!$moderation_state) {
$moderation_state = $workflow->getInitialState()->id();
$moderation_state = $workflow->getTypePlugin()->getInitialState($workflow, $entity)->id();
}
// @todo what if $entity->moderation_state is null at this point?
@ -181,8 +182,9 @@ class EntityOperations implements ContainerInjectionInterface {
]);
$content_moderation_state->workflow->target_id = $workflow->id();
}
else {
// Create a new revision.
elseif ($content_moderation_state->content_entity_revision_id->value != $entity_revision_id) {
// If a new revision of the content has been created, add a new content
// moderation state revision.
$content_moderation_state->setNewRevision(TRUE);
}
@ -234,8 +236,7 @@ class EntityOperations implements ContainerInjectionInterface {
if (!$this->moderationInfo->isLatestRevision($entity)) {
return;
}
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
if ($entity->isDefaultRevision()) {
if ($this->moderationInfo->isLiveRevision($entity)) {
return;
}
@ -250,8 +251,8 @@ class EntityOperations implements ContainerInjectionInterface {
* Check if the default revision for the given entity is published.
*
* The default revision is the same as the entity retrieved by "default" from
* the storage handler. If the entity is translated, use the default revision
* of the same language as the given entity.
* the storage handler. If the entity is translated, check if any of the
* translations are published.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity being saved.
@ -262,21 +263,22 @@ class EntityOperations implements ContainerInjectionInterface {
* TRUE if the default revision is published. FALSE otherwise.
*/
protected function isDefaultRevisionPublished(EntityInterface $entity, WorkflowInterface $workflow) {
$storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
$default_revision = $storage->load($entity->id());
$default_revision = $this->entityTypeManager->getStorage($entity->getEntityTypeId())->load($entity->id());
// Ensure we are comparing the same translation as the current entity.
// Ensure we are checking all translations of the default revision.
if ($default_revision instanceof TranslatableInterface && $default_revision->isTranslatable()) {
// If there is no translation, then there is no default revision and is
// therefore not published.
if (!$default_revision->hasTranslation($entity->language()->getId())) {
return FALSE;
// Loop through each language that has a translation.
foreach ($default_revision->getTranslationLanguages() as $language) {
// Load the translated revision.
$language_revision = $default_revision->getTranslation($language->getId());
// Return TRUE if a translation with a published state is found.
if ($workflow->getState($language_revision->moderation_state->value)->isPublishedState()) {
return TRUE;
}
}
$default_revision = $default_revision->getTranslation($entity->language()->getId());
}
return $default_revision && $workflow->getState($default_revision->moderation_state->value)->isPublishedState();
return $workflow->getState($default_revision->moderation_state->value)->isPublishedState();
}
}

View file

@ -3,6 +3,7 @@
namespace Drupal\content_moderation\Form;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\RevisionLogInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\content_moderation\ModerationInformationInterface;
@ -127,8 +128,11 @@ class EntityModerationForm extends FormBase {
$new_state = $form_state->getValue('new_state');
$entity->set('moderation_state', $new_state);
$entity->revision_log = $form_state->getValue('revision_log');
if ($entity instanceof RevisionLogInterface) {
$entity->setRevisionLogMessage($form_state->getValue('revision_log'));
$entity->setRevisionUserId($this->currentUser()->id());
}
$entity->save();
drupal_set_message($this->t('The moderation state has been updated.'));

View file

@ -98,7 +98,7 @@ class EntityRevisionConverter extends EntityConverter {
$latest_revision = $this->entityManager->getTranslationFromContext($latest_revision, NULL, ['operation' => 'entity_upcast']);
}
if ($latest_revision->isRevisionTranslationAffected()) {
if ($latest_revision instanceof EntityInterface && $latest_revision->isRevisionTranslationAffected()) {
$entity = $latest_revision;
}
}

View file

@ -109,18 +109,13 @@ class ModerationStateWidget extends OptionsSelectWidget implements ContainerFact
/** @var ContentEntityInterface $entity */
$entity = $items->getEntity();
/* @var \Drupal\Core\Config\Entity\ConfigEntityInterface $bundle_entity */
$bundle_entity = $this->entityTypeManager->getStorage($entity->getEntityType()->getBundleEntityType())->load($entity->bundle());
if (!$this->moderationInformation->isModeratedEntity($entity)) {
// @todo https://www.drupal.org/node/2779933 write a test for this.
return $element + ['#access' => FALSE];
}
$workflow = $this->moderationInformation->getWorkflowForEntity($entity);
$default = $items->get($delta)->value ? $workflow->getState($items->get($delta)->value) : $workflow->getInitialState();
if (!$default) {
throw new \UnexpectedValueException(sprintf('The %s bundle has an invalid moderation state configuration, moderation states are enabled but no default is set.', $bundle_entity->label()));
}
$default = $items->get($delta)->value ? $workflow->getState($items->get($delta)->value) : $workflow->getTypePlugin()->getInitialState($workflow, $entity);
/** @var \Drupal\workflows\Transition[] $transitions */
$transitions = $this->validator->getValidTransitions($entity, $this->currentUser);

View file

@ -37,8 +37,8 @@ class ModerationStateFieldItemList extends FieldItemList {
// It is possible that the bundle does not exist at this point. For example,
// the node type form creates a fake Node entity to get default values.
// @see \Drupal\node\NodeTypeForm::form()
$workflow = $moderation_info->getWorkflowForEntity($entity);
return $workflow ? $workflow->getInitialState()->id() : NULL;
$workflow = $moderation_info->getWorkFlowForEntity($entity);
return $workflow ? $workflow->getTypePlugin()->getInitialState($workflow, $entity)->id() : NULL;
}
/**

View file

@ -1,105 +0,0 @@
<?php
namespace Drupal\content_moderation\Plugin\Menu;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Menu\LocalTaskDefault;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\content_moderation\ModerationInformation;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a class for making the edit tab use 'Edit draft' or 'New draft'.
*/
class EditTab extends LocalTaskDefault implements ContainerFactoryPluginInterface {
use StringTranslationTrait;
/**
* The moderation information service.
*
* @var \Drupal\content_moderation\ModerationInformation
*/
protected $moderationInfo;
/**
* The entity if determinable from the route or FALSE.
*
* @var \Drupal\Core\Entity\ContentEntityInterface|FALSE
*/
protected $entity;
/**
* Constructs a new EditTab object.
*
* @param array $configuration
* Plugin configuration.
* @param string $plugin_id
* Plugin ID.
* @param mixed $plugin_definition
* Plugin definition.
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The translation service.
* @param \Drupal\content_moderation\ModerationInformation $moderation_information
* The moderation information.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, TranslationInterface $string_translation, ModerationInformation $moderation_information) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->stringTranslation = $string_translation;
$this->moderationInfo = $moderation_information;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('string_translation'),
$container->get('content_moderation.moderation_information')
);
}
/**
* {@inheritdoc}
*/
public function getRouteParameters(RouteMatchInterface $route_match) {
$entity_parameter = $route_match->getParameter($this->pluginDefinition['entity_type_id']);
$this->entity = $entity_parameter instanceof ContentEntityInterface ? $route_match->getParameter($this->pluginDefinition['entity_type_id']) : FALSE;
return parent::getRouteParameters($route_match);
}
/**
* {@inheritdoc}
*/
public function getTitle() {
// If the entity couldn't be loaded or moderation isn't enabled.
if (!$this->entity || !$this->moderationInfo->isModeratedEntity($this->entity)) {
return parent::getTitle();
}
return $this->moderationInfo->isLiveRevision($this->entity)
? $this->t('New draft')
: $this->t('Edit draft');
}
/**
* {@inheritdoc}
*/
public function getCacheTags() {
$tags = parent::getCacheTags();
// Tab changes if node or node-type is modified.
if ($this->entity) {
$tags = array_merge($tags, $this->entity->getCacheTags());
$tags[] = $this->entity->getEntityType()->getBundleEntityType() . ':' . $this->entity->bundle();
}
return $tags;
}
}

View file

@ -117,7 +117,9 @@ class ModerationStateConstraintValidator extends ConstraintValidator implements
protected function isFirstTimeModeration(EntityInterface $entity) {
$original_entity = $this->moderationInformation->getLatestRevision($entity->getEntityTypeId(), $entity->id());
$original_id = $original_entity->moderation_state;
if ($original_entity) {
$original_id = $original_entity->moderation_state;
}
return !($entity->moderation_state && $original_entity && $original_id);
}

View file

@ -4,6 +4,7 @@ namespace Drupal\content_moderation\Plugin\WorkflowType;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\EntityPublishedInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Session\AccountInterface;
@ -289,4 +290,14 @@ class ContentModeration extends WorkflowTypeBase implements ContainerFactoryPlug
return $configuration;
}
/**
* {@inheritdoc}
*/
public function getInitialState(WorkflowInterface $workflow, $entity = NULL) {
if ($entity instanceof EntityPublishedInterface) {
return $workflow->getState($entity->isPublished() ? 'published' : 'draft');
}
return parent::getInitialState($workflow);
}
}

View file

@ -81,7 +81,6 @@ class EntityModerationRouteProvider implements EntityRouteProviderInterface, Ent
// If the entity type is a node, unpublished content will be visible
// if the user has the "view all unpublished content" permission.
->setRequirement('_entity_access', "{$entity_type_id}.view")
->setRequirement('_permission', 'view latest version,view any unpublished content')
->setRequirement('_content_moderation_latest_version', 'TRUE')
->setOption('_content_moderation_entity_type', $entity_type_id)
->setOption('parameters', [

View file

@ -40,7 +40,7 @@ class StateTransitionValidation implements StateTransitionValidationInterface {
*/
public function getValidTransitions(ContentEntityInterface $entity, AccountInterface $user) {
$workflow = $this->moderationInfo->getWorkflowForEntity($entity);
$current_state = $entity->moderation_state->value ? $workflow->getState($entity->moderation_state->value) : $workflow->getInitialState();
$current_state = $entity->moderation_state->value ? $workflow->getState($entity->moderation_state->value) : $workflow->getTypePlugin()->getInitialState($workflow, $entity);
return array_filter($current_state->getTransitions(), function(Transition $transition) use ($workflow, $user) {
return $user->hasPermission('use ' . $workflow->id() . ' transition ' . $transition->id());

View file

@ -1,106 +0,0 @@
<?php
namespace Drupal\content_moderation\Tests;
/**
* Tests the moderation form, specifically on nodes.
*
* @group content_moderation
*/
class ModerationFormTest extends ModerationStateTestBase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->drupalLogin($this->adminUser);
$this->createContentTypeFromUi('Moderated content', 'moderated_content', TRUE);
$this->grantUserPermissionToCreateContentOfType($this->adminUser, 'moderated_content');
}
/**
* Tests the moderation form that shows on the latest version page.
*
* The latest version page only shows if there is a forward revision. There
* is only a forward revision if a draft revision is created on a node where
* the default revision is not a published moderation state.
*
* @see \Drupal\content_moderation\EntityOperations
* @see \Drupal\content_moderation\Tests\ModerationStateBlockTest::testCustomBlockModeration
*/
public function testModerationForm() {
// Create new moderated content in draft.
$this->drupalPostForm('node/add/moderated_content', [
'title[0][value]' => 'Some moderated content',
'body[0][value]' => 'First version of the content.',
], t('Save and Create New Draft'));
$node = $this->drupalGetNodeByTitle('Some moderated content');
$canonical_path = sprintf('node/%d', $node->id());
$edit_path = sprintf('node/%d/edit', $node->id());
$latest_version_path = sprintf('node/%d/latest', $node->id());
$this->assertTrue($this->adminUser->hasPermission('edit any moderated_content content'));
// The latest version page should not show, because there is no forward
// revision.
$this->drupalGet($latest_version_path);
$this->assertResponse(403);
// Update the draft.
$this->drupalPostForm($edit_path, [
'body[0][value]' => 'Second version of the content.',
], t('Save and Create New Draft'));
// The latest version page should not show, because there is still no
// forward revision.
$this->drupalGet($latest_version_path);
$this->assertResponse(403);
// Publish the draft.
$this->drupalPostForm($edit_path, [
'body[0][value]' => 'Third version of the content.',
], t('Save and Publish'));
// The published view should not have a moderation form, because it is the
// default revision.
$this->drupalGet($canonical_path);
$this->assertResponse(200);
$this->assertNoText('Status', 'The node view page has no moderation form.');
// The latest version page should not show, because there is still no
// forward revision.
$this->drupalGet($latest_version_path);
$this->assertResponse(403);
// Make a forward revision.
$this->drupalPostForm($edit_path, [
'body[0][value]' => 'Fourth version of the content.',
], t('Save and Create New Draft'));
// The published view should not have a moderation form, because it is the
// default revision.
$this->drupalGet($canonical_path);
$this->assertResponse(200);
$this->assertNoText('Status', 'The node view page has no moderation form.');
// The latest version page should show the moderation form and have "Draft"
// status, because the forward revision is in "Draft".
$this->drupalGet($latest_version_path);
$this->assertResponse(200);
$this->assertText('Status', 'Form text found on the latest-version page.');
$this->assertText('Draft', 'Correct status found on the latest-version page.');
// Submit the moderation form to change status to published.
$this->drupalPostForm($latest_version_path, [
'new_state' => 'published',
], t('Apply'));
// The latest version page should not show, because there is no
// forward revision.
$this->drupalGet($latest_version_path);
$this->assertResponse(403);
}
}

View file

@ -1,146 +0,0 @@
<?php
namespace Drupal\content_moderation\Tests;
use Drupal\Core\Session\AccountInterface;
use Drupal\simpletest\WebTestBase;
use Drupal\user\Entity\Role;
/**
* Defines a base class for moderation state tests.
*
* @deprecated Scheduled for removal in Drupal 9.0.0.
* Use \Drupal\Tests\content_moderation\Functional\ModerationStateTestBase instead.
*/
abstract class ModerationStateTestBase extends WebTestBase {
/**
* Profile to use.
*/
protected $profile = 'testing';
/**
* Admin user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $adminUser;
/**
* Permissions to grant admin user.
*
* @var array
*/
protected $permissions = [
'administer content moderation',
'access administration pages',
'administer content types',
'administer nodes',
'view latest version',
'view any unpublished content',
'access content overview',
'use editorial transition create_new_draft',
'use editorial transition publish',
];
/**
* Modules to enable.
*
* @var array
*/
public static $modules = [
'content_moderation',
'block',
'block_content',
'node',
];
/**
* Sets the test up.
*/
protected function setUp() {
parent::setUp();
$this->adminUser = $this->drupalCreateUser($this->permissions);
$this->drupalPlaceBlock('local_tasks_block', ['id' => 'tabs_block']);
$this->drupalPlaceBlock('page_title_block');
$this->drupalPlaceBlock('local_actions_block', ['id' => 'actions_block']);
}
/**
* Gets the permission machine name for a transition.
*
* @param string $workflow_id
* The workflow ID.
* @param string $transition_id
* The transition ID.
*
* @return string
* The permission machine name for a transition.
*/
protected function getWorkflowTransitionPermission($workflow_id, $transition_id) {
return 'use ' . $workflow_id . ' transition ' . $transition_id;
}
/**
* Creates a content-type from the UI.
*
* @param string $content_type_name
* Content type human name.
* @param string $content_type_id
* Machine name.
* @param bool $moderated
* TRUE if should be moderated.
* @param string $workflow_id
* The workflow to attach to the bundle.
*/
protected function createContentTypeFromUi($content_type_name, $content_type_id, $moderated = FALSE, $workflow_id = 'editorial') {
$this->drupalGet('admin/structure/types');
$this->clickLink('Add content type');
$edit = [
'name' => $content_type_name,
'type' => $content_type_id,
];
$this->drupalPostForm(NULL, $edit, t('Save content type'));
if ($moderated) {
$this->enableModerationThroughUi($content_type_id, $workflow_id);
}
}
/**
* Enable moderation for a specified content type, using the UI.
*
* @param string $content_type_id
* Machine name.
* @param string $workflow_id
* The workflow to attach to the bundle.
*/
protected function enableModerationThroughUi($content_type_id, $workflow_id = 'editorial') {
$edit['workflow'] = $workflow_id;
$this->drupalPostForm('admin/structure/types/manage/' . $content_type_id . '/moderation', $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();
}
/**
* Grants given user permission to create content of given type.
*
* @param \Drupal\Core\Session\AccountInterface $account
* User to grant permission to.
* @param string $content_type_id
* Content type ID.
*/
protected function grantUserPermissionToCreateContentOfType(AccountInterface $account, $content_type_id) {
$role_ids = $account->getRoles(TRUE);
/* @var \Drupal\user\RoleInterface $role */
$role_id = reset($role_ids);
$role = Role::load($role_id);
$role->grantPermission(sprintf('create %s content', $content_type_id));
$role->grantPermission(sprintf('edit any %s content', $content_type_id));
$role->grantPermission(sprintf('delete any %s content', $content_type_id));
$role->save();
}
}

View file

@ -1,4 +1,4 @@
{{ attach_library('content_moderation/entity-moderation-form') }}
{{ attach_library('content_moderation/content_moderation') }}
<ul class="entity-moderation-form">
<li>{{ form.current }}</li>
<li>{{ form.new_state }}</li>

View file

@ -1,96 +0,0 @@
<?php
namespace Drupal\Tests\content_moderation\Functional;
use Drupal\simpletest\ContentTypeCreationTrait;
use Drupal\simpletest\NodeCreationTrait;
use Drupal\Tests\BrowserTestBase;
use Drupal\workflows\Entity\Workflow;
/**
* Test the content moderation local task.
*
* @group content_moderation
*/
class LocalTaskTest extends BrowserTestBase {
use ContentTypeCreationTrait;
use NodeCreationTrait;
/**
* {@inheritdoc}
*/
public static $modules = [
'content_moderation_test_local_task',
'content_moderation',
'block',
];
/**
* A test node.
*
* @var \Drupal\node\NodeInterface
*/
protected $testNode;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('local_tasks_block', ['id' => 'tabs_block']);
$this->drupalLogin($this->createUser(['bypass node access']));
$node_type = $this->createContentType([
'type' => 'test_content_type',
]);
// Now enable moderation for subsequent nodes.
$workflow = Workflow::load('editorial');
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', $node_type->id());
$workflow->save();
$this->testNode = $this->createNode([
'type' => $node_type->id(),
'moderation_state' => 'draft',
]);
}
/**
* Tests local tasks behave with content_moderation enabled.
*/
public function testLocalTasks() {
// The default state is a draft.
$this->drupalGet(sprintf('node/%s', $this->testNode->id()));
$this->assertTasks('Edit draft');
// When published as the live revision, the label changes.
$this->testNode->moderation_state = 'published';
$this->testNode->save();
$this->drupalGet(sprintf('node/%s', $this->testNode->id()));
$this->assertTasks('New draft');
$tags = $this->drupalGetHeader('X-Drupal-Cache-Tags');
$this->assertContains('node:1', $tags);
$this->assertContains('node_type:test_content_type', $tags);
// Without an upcast node, the state cannot be determined.
$this->clickLink('Task Without Upcast Node');
$this->assertTasks('Edit');
}
/**
* Assert the correct tasks appear.
*
* @param string $edit_tab_label
* The edit tab label to assert.
*/
protected function assertTasks($edit_tab_label) {
$this->assertSession()->linkExists('View');
$this->assertSession()->linkExists('Task Without Upcast Node');
$this->assertSession()->linkExists($edit_tab_label);
$this->assertSession()->linkExists('Delete');
}
}

View file

@ -0,0 +1,208 @@
<?php
namespace Drupal\Tests\content_moderation\Functional;
use Drupal\workflows\Entity\Workflow;
/**
* Tests the moderation form, specifically on nodes.
*
* @group content_moderation
*/
class ModerationFormTest extends ModerationStateTestBase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->drupalLogin($this->adminUser);
$this->createContentTypeFromUi('Moderated content', 'moderated_content', TRUE);
$this->grantUserPermissionToCreateContentOfType($this->adminUser, 'moderated_content');
}
/**
* Tests the moderation form that shows on the latest version page.
*
* The latest version page only shows if there is a forward revision. There
* is only a forward revision if a draft revision is created on a node where
* the default revision is not a published moderation state.
*
* @see \Drupal\content_moderation\EntityOperations
* @see \Drupal\Tests\content_moderation\Functional\ModerationStateBlockTest::testCustomBlockModeration
*/
public function testModerationForm() {
// Create new moderated content in draft.
$this->drupalPostForm('node/add/moderated_content', [
'title[0][value]' => 'Some moderated content',
'body[0][value]' => 'First version of the content.',
], t('Save and Create New Draft'));
$node = $this->drupalGetNodeByTitle('Some moderated content');
$canonical_path = sprintf('node/%d', $node->id());
$edit_path = sprintf('node/%d/edit', $node->id());
$latest_version_path = sprintf('node/%d/latest', $node->id());
$this->assertTrue($this->adminUser->hasPermission('edit any moderated_content content'));
// The canonical view should have a moderation form, because it is not the
// live revision.
$this->drupalGet($canonical_path);
$this->assertResponse(200);
$this->assertField('edit-new-state', 'The node view page has a moderation form.');
// The latest version page should not show, because there is no forward
// revision.
$this->drupalGet($latest_version_path);
$this->assertResponse(403);
// Update the draft.
$this->drupalPostForm($edit_path, [
'body[0][value]' => 'Second version of the content.',
], t('Save and Create New Draft'));
// The canonical view should have a moderation form, because it is not the
// live revision.
$this->drupalGet($canonical_path);
$this->assertResponse(200);
$this->assertField('edit-new-state', 'The node view page has a moderation form.');
// The latest version page should not show, because there is still no
// forward revision.
$this->drupalGet($latest_version_path);
$this->assertResponse(403);
// Publish the draft.
$this->drupalPostForm($edit_path, [
'body[0][value]' => 'Third version of the content.',
], t('Save and Publish'));
// The published view should not have a moderation form, because it is the
// live revision.
$this->drupalGet($canonical_path);
$this->assertResponse(200);
$this->assertNoField('edit-new-state', 'The node view page has no moderation form.');
// The latest version page should not show, because there is still no
// forward revision.
$this->drupalGet($latest_version_path);
$this->assertResponse(403);
// Make a forward revision.
$this->drupalPostForm($edit_path, [
'body[0][value]' => 'Fourth version of the content.',
], t('Save and Create New Draft'));
// The published view should not have a moderation form, because it is the
// live revision.
$this->drupalGet($canonical_path);
$this->assertResponse(200);
$this->assertNoField('edit-new-state', 'The node view page has no moderation form.');
// The latest version page should show the moderation form and have "Draft"
// status, because the forward revision is in "Draft".
$this->drupalGet($latest_version_path);
$this->assertResponse(200);
$this->assertField('edit-new-state', 'The latest-version page has a moderation form.');
$this->assertText('Draft', 'Correct status found on the latest-version page.');
// Submit the moderation form to change status to published.
$this->drupalPostForm($latest_version_path, [
'new_state' => 'published',
], t('Apply'));
// The latest version page should not show, because there is no
// forward revision.
$this->drupalGet($latest_version_path);
$this->assertResponse(403);
}
/**
* Test moderation non-bundle entity type.
*/
public function testNonBundleModerationForm() {
$this->drupalLogin($this->rootUser);
$workflow = Workflow::load('editorial');
$workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_mulrevpub', 'entity_test_mulrevpub');
$workflow->save();
// Create new moderated content in draft.
$this->drupalPostForm('entity_test_mulrevpub/add', [], t('Save and Create New Draft'));
// The latest version page should not show, because there is no forward
// revision.
$this->drupalGet('/entity_test_mulrevpub/manage/1/latest');
$this->assertResponse(403);
// Update the draft.
$this->drupalPostForm('entity_test_mulrevpub/manage/1/edit', [], t('Save and Create New Draft'));
// The latest version page should not show, because there is still no
// forward revision.
$this->drupalGet('/entity_test_mulrevpub/manage/1/latest');
$this->assertResponse(403);
// Publish the draft.
$this->drupalPostForm('entity_test_mulrevpub/manage/1/edit', [], t('Save and Publish'));
// The published view should not have a moderation form, because it is the
// default revision.
$this->drupalGet('entity_test_mulrevpub/manage/1');
$this->assertResponse(200);
$this->assertNoText('Status', 'The node view page has no moderation form.');
// The latest version page should not show, because there is still no
// forward revision.
$this->drupalGet('entity_test_mulrevpub/manage/1/latest');
$this->assertResponse(403);
// Make a forward revision.
$this->drupalPostForm('entity_test_mulrevpub/manage/1/edit', [], t('Save and Create New Draft'));
// The published view should not have a moderation form, because it is the
// default revision.
$this->drupalGet('entity_test_mulrevpub/manage/1');
$this->assertResponse(200);
$this->assertNoText('Status', 'The node view page has no moderation form.');
// The latest version page should show the moderation form and have "Draft"
// status, because the forward revision is in "Draft".
$this->drupalGet('entity_test_mulrevpub/manage/1/latest');
$this->assertResponse(200);
$this->assertText('Status', 'Form text found on the latest-version page.');
$this->assertText('Draft', 'Correct status found on the latest-version page.');
// Submit the moderation form to change status to published.
$this->drupalPostForm('entity_test_mulrevpub/manage/1/latest', [
'new_state' => 'published',
], t('Apply'));
// The latest version page should not show, because there is no
// forward revision.
$this->drupalGet('entity_test_mulrevpub/manage/1/latest');
$this->assertResponse(403);
}
/**
* Tests the revision author is updated when the moderation form is used.
*/
public function testModerationFormSetsRevisionAuthor() {
// Create new moderated content in published.
$node = $this->createNode(['type' => 'moderated_content', 'moderation_state' => 'published']);
// Make a forward revision.
$node->title = $this->randomMachineName();
$node->moderation_state->value = 'draft';
$node->save();
$another_user = $this->drupalCreateUser($this->permissions);
$this->grantUserPermissionToCreateContentOfType($another_user, 'moderated_content');
$this->drupalLogin($another_user);
$this->drupalPostForm(sprintf('node/%d/latest', $node->id()), [
'new_state' => 'published',
], t('Apply'));
$this->drupalGet(sprintf('node/%d/revisions', $node->id()));
$this->assertText('by ' . $another_user->getAccountName());
}
}

View file

@ -0,0 +1,90 @@
<?php
namespace Drupal\Tests\content_moderation\Functional;
use Drupal\simpletest\ContentTypeCreationTrait;
use Drupal\Tests\BrowserTestBase;
use Drupal\workflows\Entity\Workflow;
/**
* Test revision revert.
*
* @group content_moderation
*/
class ModerationRevisionRevertTest extends BrowserTestBase {
use ContentTypeCreationTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = [
'content_moderation',
'node',
];
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
$moderated_bundle = $this->createContentType(['type' => 'moderated_bundle']);
$moderated_bundle->save();
$workflow = Workflow::load('editorial');
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'moderated_bundle');
$workflow->save();
$admin = $this->drupalCreateUser([
'access content overview',
'administer nodes',
'bypass node access',
'view all revisions',
'view content moderation',
'use editorial transition create_new_draft',
'use editorial transition publish',
]);
$this->drupalLogin($admin);
}
/**
* Test that reverting a revision works.
*/
public function testEditingAfterRevertRevision() {
// Create a draft.
$this->drupalPostForm('node/add/moderated_bundle', ['title[0][value]' => 'First draft node'], t('Save and Create New Draft'));
// Now make it published.
$this->drupalPostForm('node/1/edit', ['title[0][value]' => 'Published node'], t('Save and Publish'));
// Check the editing form that show the published title.
$this->drupalGet('node/1/edit');
$this->assertSession()
->pageTextContains('Published node');
// Revert the first revision.
$revision_url = 'node/1/revisions/1/revert';
$this->drupalGet($revision_url);
$this->assertSession()->elementExists('css', '.form-submit');
$this->click('.form-submit');
// Check that it reverted.
$this->drupalGet('node/1/edit');
$this->assertSession()
->pageTextContains('First draft node');
// Try to save the node.
$this->click('.moderation-state-draft > input');
// Check if the submission passed the EntityChangedConstraintValidator.
$this->assertSession()
->pageTextNotContains('The content has either been modified by another user, or you have already submitted modifications. As a result, your changes cannot be saved.');
// Check the node has been saved.
$this->assertSession()
->pageTextContains('moderated_bundle First draft node has been updated');
}
}

View file

@ -1,6 +1,6 @@
<?php
namespace Drupal\content_moderation\Tests;
namespace Drupal\Tests\content_moderation\Functional;
use Drupal\block_content\Entity\BlockContent;
use Drupal\block_content\Entity\BlockContentType;

View file

@ -1,6 +1,6 @@
<?php
namespace Drupal\content_moderation\Tests;
namespace Drupal\Tests\content_moderation\Functional;
use Drupal\Core\Url;
use Drupal\node\Entity\Node;
@ -132,7 +132,7 @@ class ModerationStateNodeTest extends ModerationStateTestBase {
$this->drupalLogin($this->adminUser);
$this->drupalGet('admin/content');
$element = $this->cssSelect('nav.pager li.is-active a');
$url = (string) $element[0]['href'];
$url = $element[0]->getAttribute('href');
$query = [];
parse_str(parse_url($url, PHP_URL_QUERY), $query);
$this->assertEqual(0, $query['page']);

View file

@ -1,7 +1,6 @@
<?php
namespace Drupal\content_moderation\Tests;
namespace Drupal\Tests\content_moderation\Functional;
/**
* Tests moderation state node type integration.
@ -30,10 +29,25 @@ class ModerationStateNodeTypeTest extends ModerationStateTestBase {
* Tests enabling moderation on an existing node-type, with content.
*/
public function testEnablingOnExistingContent() {
$editor_permissions = [
'administer content moderation',
'access administration pages',
'administer content types',
'administer nodes',
'view latest version',
'view any unpublished content',
'access content overview',
'use editorial transition create_new_draft',
];
$publish_permissions = array_merge($editor_permissions, ['use editorial transition publish']);
$editor = $this->drupalCreateUser($editor_permissions);
$editor_with_publish = $this->drupalCreateUser($publish_permissions);
// Create a node type that is not moderated.
$this->drupalLogin($this->adminUser);
$this->drupalLogin($editor);
$this->createContentTypeFromUi('Not moderated', 'not_moderated');
$this->grantUserPermissionToCreateContentOfType($this->adminUser, 'not_moderated');
$this->grantUserPermissionToCreateContentOfType($editor, 'not_moderated');
$this->grantUserPermissionToCreateContentOfType($editor_with_publish, 'not_moderated');
// Create content.
$this->drupalGet('node/add/not_moderated');
@ -68,7 +82,13 @@ class ModerationStateNodeTypeTest extends ModerationStateTestBase {
$this->drupalGet('node/' . $node->id() . '/edit');
$this->assertResponse(200);
$this->assertRaw('Save and Create New Draft');
$this->assertNoRaw('Save and publish');
$this->assertNoRaw('Save and Publish');
$this->drupalLogin($editor_with_publish);
$this->drupalGet('node/' . $node->id() . '/edit');
$this->assertResponse(200);
$this->assertRaw('Save and Create New Draft');
$this->assertRaw('Save and Publish');
}
}

View file

@ -50,6 +50,7 @@ abstract class ModerationStateTestBase extends BrowserTestBase {
'block',
'block_content',
'node',
'entity_test',
];
/**

View file

@ -9,6 +9,37 @@ namespace Drupal\Tests\content_moderation\Functional;
*/
class NodeAccessTest extends ModerationStateTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = [
'content_moderation',
'block',
'block_content',
'node',
'node_access_test_empty',
];
/**
* Permissions to grant admin user.
*
* @var array
*/
protected $permissions = [
'administer content moderation',
'access administration pages',
'administer content types',
'administer nodes',
'view latest version',
'view any unpublished content',
'access content overview',
'use editorial transition create_new_draft',
'use editorial transition publish',
'bypass node access',
];
/**
* {@inheritdoc}
*/
@ -17,6 +48,10 @@ class NodeAccessTest extends ModerationStateTestBase {
$this->drupalLogin($this->adminUser);
$this->createContentTypeFromUi('Moderated content', 'moderated_content', TRUE);
$this->grantUserPermissionToCreateContentOfType($this->adminUser, 'moderated_content');
// Rebuild permissions because hook_node_grants() is implemented by the
// node_access_test_empty module.
node_access_rebuild();
}
/**
@ -38,7 +73,24 @@ class NodeAccessTest extends ModerationStateTestBase {
$edit_path = 'node/' . $node->id() . '/edit';
$latest_path = 'node/' . $node->id() . '/latest';
// Now make a new user and verify that the new user's access is correct.
$user = $this->createUser([
'use editorial transition create_new_draft',
'view latest version',
'view any unpublished content',
]);
$this->drupalLogin($user);
$this->drupalGet($edit_path);
$this->assertResponse(403);
$this->drupalGet($latest_path);
$this->assertResponse(403);
$this->drupalGet($view_path);
$this->assertResponse(200);
// Publish the node.
$this->drupalLogin($this->adminUser);
$this->drupalPostForm($edit_path, [], t('Save and Publish'));
// Ensure access works correctly for anonymous users.
@ -58,12 +110,6 @@ class NodeAccessTest extends ModerationStateTestBase {
'title[0][value]' => 'moderated content revised',
], t('Save and Create New Draft'));
// Now make a new user and verify that the new user's access is correct.
$user = $this->createUser([
'use editorial transition create_new_draft',
'view latest version',
'view any unpublished content',
]);
$this->drupalLogin($user);
$this->drupalGet($edit_path);

View file

@ -0,0 +1,54 @@
<?php
namespace Drupal\Tests\content_moderation\Kernel;
use Drupal\content_moderation\Entity\ContentModerationState;
use Drupal\KernelTests\KernelTestBase;
/**
* @coversDefaultClass \Drupal\content_moderation\ContentModerationStateAccessControlHandler
* @group content_moderation
*/
class ContentModerationStateAccessControlHandlerTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = [
'content_moderation',
'workflows',
'user',
];
/**
* The content_moderation_state access control handler.
*
* @var \Drupal\Core\Entity\EntityAccessControlHandlerInterface
*/
protected $accessControlHandler;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('content_moderation_state');
$this->installEntitySchema('user');
$this->accessControlHandler = $this->container->get('entity_type.manager')->getAccessControlHandler('content_moderation_state');
}
/**
* @covers ::checkAccess
* @covers ::checkCreateAccess
*/
public function testHandler() {
$entity = ContentModerationState::create([]);
$this->assertFalse($this->accessControlHandler->access($entity, 'view'));
$this->assertFalse($this->accessControlHandler->access($entity, 'update'));
$this->assertFalse($this->accessControlHandler->access($entity, 'delete'));
$this->assertFalse($this->accessControlHandler->createAccess());
}
}

View file

@ -0,0 +1,142 @@
<?php
namespace Drupal\Tests\content_moderation\Kernel;
use Drupal\content_moderation\Entity\ContentModerationState;
use Drupal\KernelTests\KernelTestBase;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\workflows\Entity\Workflow;
/**
* Test the ContentModerationState storage schema.
*
* @coversDefaultClass \Drupal\content_moderation\ContentModerationStateStorageSchema
* @group content_moderation
*/
class ContentModerationStateStorageSchemaTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = [
'node',
'content_moderation',
'user',
'system',
'text',
'workflows',
'entity_test',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('node', 'node_access');
$this->installEntitySchema('node');
$this->installEntitySchema('entity_test');
$this->installEntitySchema('user');
$this->installEntitySchema('content_moderation_state');
$this->installConfig('content_moderation');
NodeType::create([
'type' => 'example',
])->save();
$workflow = Workflow::load('editorial');
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
$workflow->save();
}
/**
* Test the ContentModerationState unique keys.
*
* @covers ::getEntitySchema
*/
public function testUniqueKeys() {
// Create a node which will create a new ContentModerationState entity.
$node = Node::create([
'title' => 'Test title',
'type' => 'example',
'moderation_state' => 'draft',
]);
$node->save();
// Ensure an exception when all values match.
$this->assertStorageException([
'content_entity_type_id' => $node->getEntityTypeId(),
'content_entity_id' => $node->id(),
'content_entity_revision_id' => $node->getRevisionId(),
], TRUE);
// No exception for the same values, with a different langcode.
$this->assertStorageException([
'content_entity_type_id' => $node->getEntityTypeId(),
'content_entity_id' => $node->id(),
'content_entity_revision_id' => $node->getRevisionId(),
'langcode' => 'de',
], FALSE);
// A different workflow should not trigger an exception.
$this->assertStorageException([
'content_entity_type_id' => $node->getEntityTypeId(),
'content_entity_id' => $node->id(),
'content_entity_revision_id' => $node->getRevisionId(),
'workflow' => 'foo',
], FALSE);
// Different entity types should not trigger an exception.
$this->assertStorageException([
'content_entity_type_id' => 'entity_test',
'content_entity_id' => $node->id(),
'content_entity_revision_id' => $node->getRevisionId(),
], FALSE);
// Different entity and revision IDs should not trigger an exception.
$this->assertStorageException([
'content_entity_type_id' => $node->getEntityTypeId(),
'content_entity_id' => 9999,
'content_entity_revision_id' => 9999,
], FALSE);
// Creating a version of the entity with a previously used, but not current
// revision ID should trigger an exception.
$old_revision_id = $node->getRevisionId();
$node->setNewRevision(TRUE);
$node->title = 'Updated title';
$node->moderation_state = 'published';
$node->save();
$this->assertStorageException([
'content_entity_type_id' => $node->getEntityTypeId(),
'content_entity_id' => $node->id(),
'content_entity_revision_id' => $old_revision_id,
], TRUE);
}
/**
* Assert if a storage exception is triggered when saving a given entity.
*
* @param array $values
* An array of entity values.
* @param bool $has_exception
* If an exception should be triggered when saving the entity.
*/
protected function assertStorageException(array $values, $has_exception) {
$defaults = [
'moderation_state' => 'draft',
'workflow' => 'editorial',
];
$entity = ContentModerationState::create($values + $defaults);
$exception_triggered = FALSE;
try {
ContentModerationState::updateOrCreateFromEntity($entity);
}
catch (\Exception $e) {
$exception_triggered = TRUE;
}
$this->assertEquals($has_exception, $exception_triggered);
}
}

View file

@ -53,6 +53,7 @@ class ContentModerationStateTest extends KernelTestBase {
$this->installEntitySchema('user');
$this->installEntitySchema('entity_test_with_bundle');
$this->installEntitySchema('entity_test_rev');
$this->installEntitySchema('entity_test_no_bundle');
$this->installEntitySchema('entity_test_mulrevpub');
$this->installEntitySchema('block_content');
$this->installEntitySchema('content_moderation_state');
@ -94,6 +95,9 @@ class ContentModerationStateTest extends KernelTestBase {
'title' => 'Test title',
$this->entityTypeManager->getDefinition($entity_type_id)->getKey('bundle') => $bundle_id,
]);
if ($entity instanceof EntityPublishedInterface) {
$entity->setUnpublished();
}
$entity->save();
$entity = $this->reloadEntity($entity);
$this->assertEquals('draft', $entity->moderation_state->value);
@ -178,7 +182,10 @@ class ContentModerationStateTest extends KernelTestBase {
],
'Entity Test with revisions' => [
'entity_test_rev',
]
],
'Entity without bundle' => [
'entity_test_no_bundle',
],
];
}
@ -400,6 +407,7 @@ class ContentModerationStateTest extends KernelTestBase {
// Test both a config and non-config based bundle and entity type.
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
$workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_rev', 'entity_test_rev');
$workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_no_bundle', 'entity_test_no_bundle');
$workflow->save();
$this->assertEquals([
@ -412,9 +420,11 @@ class ContentModerationStateTest extends KernelTestBase {
],
], $workflow->getDependencies());
$entity_types = $workflow->getTypePlugin()->getEntityTypes();
$this->assertTrue(in_array('node', $entity_types));
$this->assertTrue(in_array('entity_test_rev', $entity_types));
$this->assertEquals([
'entity_test_no_bundle',
'entity_test_rev',
'node'
], $workflow->getTypePlugin()->getEntityTypes());
// Delete the node type and ensure it is removed from the workflow.
$node_type->delete();
@ -426,7 +436,7 @@ class ContentModerationStateTest extends KernelTestBase {
$this->container->get('config.manager')->uninstall('module', 'entity_test');
$workflow = Workflow::load('editorial');
$entity_types = $workflow->getTypePlugin()->getEntityTypes();
$this->assertFalse(in_array('entity_test_rev', $entity_types));
$this->assertEquals([], $entity_types);
}
/**

View file

@ -0,0 +1,109 @@
<?php
namespace Drupal\Tests\content_moderation\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\workflows\Entity\Workflow;
/**
* Tests the correct default revision is set.
*
* @group content_moderation
*/
class DefaultRevisionStateTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = [
'entity_test',
'node',
'block_content',
'content_moderation',
'user',
'system',
'language',
'content_translation',
'text',
'workflows',
];
/**
* @var \Drupal\Core\Entity\EntityTypeManager
*/
protected $entityTypeManager;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('node', 'node_access');
$this->installEntitySchema('node');
$this->installEntitySchema('user');
$this->installEntitySchema('entity_test_with_bundle');
$this->installEntitySchema('entity_test_rev');
$this->installEntitySchema('entity_test_mulrevpub');
$this->installEntitySchema('block_content');
$this->installEntitySchema('content_moderation_state');
$this->installConfig('content_moderation');
$this->entityTypeManager = $this->container->get('entity_type.manager');
}
/**
* Tests a translatable Node.
*/
public function testMultilingual() {
// Enable French.
ConfigurableLanguage::createFromLangcode('fr')->save();
$node_type = NodeType::create([
'type' => 'example',
]);
$node_type->save();
$this->container->get('content_translation.manager')->setEnabled('node', 'example', TRUE);
$workflow = Workflow::load('editorial');
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
$workflow->save();
$english_node = Node::create([
'type' => 'example',
'title' => 'Test title',
]);
// Revision 1 (en).
$english_node
->setUnpublished()
->save();
$this->assertEquals('draft', $english_node->moderation_state->value);
$this->assertFalse($english_node->isPublished());
$this->assertTrue($english_node->isDefaultRevision());
// Revision 2 (fr)
$french_node = $english_node->addTranslation('fr', ['title' => 'French title']);
$french_node->moderation_state->value = 'published';
$french_node->save();
$this->assertTrue($french_node->isPublished());
$this->assertTrue($french_node->isDefaultRevision());
// Revision 3 (fr)
$node = Node::load($english_node->id())->getTranslation('fr');
$node->moderation_state->value = 'draft';
$node->save();
$this->assertFalse($node->isPublished());
$this->assertFalse($node->isDefaultRevision());
// Revision 4 (en)
$latest_revision = $this->entityTypeManager->getStorage('node')->loadRevision(3);
$latest_revision->moderation_state->value = 'draft';
$latest_revision->save();
$this->assertFalse($latest_revision->isPublished());
$this->assertFalse($latest_revision->isDefaultRevision());
}
}

View file

@ -0,0 +1,82 @@
<?php
namespace Drupal\Tests\content_moderation\Kernel;
use Drupal\entity_test\Entity\EntityTestRev;
use Drupal\KernelTests\KernelTestBase;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\workflows\Entity\Workflow;
/**
* Tests the correct initial states are set on install.
*
* @group content_moderation
*/
class InitialStateTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = [
'entity_test',
'node',
'user',
'system',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('node', 'node_access');
$this->installEntitySchema('node');
$this->installEntitySchema('user');
$this->installEntitySchema('entity_test_rev');
}
/**
* Tests the correct initial state.
*/
public function testInitialState() {
$node_type = NodeType::create([
'type' => 'example',
]);
$node_type->save();
// Test with an entity type that implements EntityPublishedInterface.
$unpublished_node = Node::create([
'type' => 'example',
'title' => 'Unpublished node',
'status' => 0,
]);
$unpublished_node->save();
$published_node = Node::create([
'type' => 'example',
'title' => 'Published node',
'status' => 1,
]);
$published_node->save();
// Test with an entity type that doesn't implement EntityPublishedInterface.
$entity_test = EntityTestRev::create();
$entity_test->save();
\Drupal::service('module_installer')->install(['content_moderation'], TRUE);
$workflow = Workflow::load('editorial');
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
$workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_rev', 'entity_test_rev');
$workflow->save();
$loaded_unpublished_node = Node::load($unpublished_node->id());
$loaded_published_node = Node::load($published_node->id());
$loaded_entity_test = EntityTestRev::load($entity_test->id());
$this->assertEquals('draft', $loaded_unpublished_node->moderation_state->value);
$this->assertEquals('published', $loaded_published_node->moderation_state->value);
$this->assertEquals('draft', $loaded_entity_test->moderation_state->value);
}
}

Some files were not shown because too many files have changed in this diff Show more