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

@ -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

@ -0,0 +1,133 @@
<?php
namespace Drupal\Tests\content_moderation\Functional;
use Drupal\block_content\Entity\BlockContent;
use Drupal\block_content\Entity\BlockContentType;
/**
* Tests general content moderation workflow for blocks.
*
* @group content_moderation
*/
class ModerationStateBlockTest extends ModerationStateTestBase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create the "basic" block type.
$bundle = BlockContentType::create([
'id' => 'basic',
'label' => 'basic',
'revision' => FALSE,
]);
$bundle->save();
// Add the body field to it.
block_content_add_body_field($bundle->id());
}
/**
* Tests moderating custom blocks.
*
* Blocks and any non-node-type-entities do not have a concept of
* "published". As such, we must use the "default revision" to know what is
* going to be "published", i.e. visible to the user.
*
* The one exception is a block that has never been "published". When a block
* is first created, it becomes the "default revision". For each edit of the
* block after that, Content Moderation checks the "default revision" to
* see if it is set to a published moderation state. If it is not, the entity
* being saved will become the "default revision".
*
* The test below is intended, in part, to make this behavior clear.
*
* @see \Drupal\content_moderation\EntityOperations::entityPresave
* @see \Drupal\content_moderation\Tests\ModerationFormTest::testModerationForm
*/
public function testCustomBlockModeration() {
$this->drupalLogin($this->rootUser);
$this->drupalGet('admin/structure/block/block-content/types');
$this->assertLinkByHref('admin/structure/block/block-content/manage/basic/moderation');
$this->drupalGet('admin/structure/block/block-content/manage/basic');
$this->assertLinkByHref('admin/structure/block/block-content/manage/basic/moderation');
$this->drupalGet('admin/structure/block/block-content/manage/basic/moderation');
// Enable moderation for custom blocks at
// admin/structure/block/block-content/manage/basic/moderation.
$edit = ['workflow' => 'editorial'];
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertText(t('Your settings have been saved.'));
// Create a custom block at block/add and save it as draft.
$body = 'Body of moderated block';
$edit = [
'info[0][value]' => 'Moderated block',
'body[0][value]' => $body,
];
$this->drupalPostForm('block/add', $edit, t('Save and Create New Draft'));
$this->assertText(t('basic Moderated block has been created.'));
// Place the block in the Sidebar First region.
$instance = [
'id' => 'moderated_block',
'settings[label]' => $edit['info[0][value]'],
'region' => 'sidebar_first',
];
$block = BlockContent::load(1);
$url = 'admin/structure/block/add/block_content:' . $block->uuid() . '/' . $this->config('system.theme')->get('default');
$this->drupalPostForm($url, $instance, t('Save block'));
// Navigate to home page and check that the block is visible. It should be
// visible because it is the default revision.
$this->drupalGet('');
$this->assertText($body);
// Update the block.
$updated_body = 'This is the new body value';
$edit = [
'body[0][value]' => $updated_body,
];
$this->drupalPostForm('block/' . $block->id(), $edit, t('Save and Create New Draft'));
$this->assertText(t('basic Moderated block has been updated.'));
// Navigate to the home page and check that the block shows the updated
// content. It should show the updated content because the block's default
// revision is not a published moderation state.
$this->drupalGet('');
$this->assertText($updated_body);
// Publish the block so we can create a forward revision.
$this->drupalPostForm('block/' . $block->id(), [], t('Save and Publish'));
// Create a forward revision.
$forward_revision_body = 'This is the forward revision body value';
$edit = [
'body[0][value]' => $forward_revision_body,
];
$this->drupalPostForm('block/' . $block->id(), $edit, t('Save and Create New Draft'));
$this->assertText(t('basic Moderated block has been updated.'));
// Navigate to home page and check that the forward revision doesn't show,
// since it should not be set as the default revision.
$this->drupalGet('');
$this->assertText($updated_body);
// Open the latest tab and publish the new draft.
$edit = [
'new_state' => 'published',
];
$this->drupalPostForm('block/' . $block->id() . '/latest', $edit, t('Apply'));
$this->assertText(t('The moderation state has been updated.'));
// Navigate to home page and check that the forward revision is now the
// default revision and therefore visible.
$this->drupalGet('');
$this->assertText($forward_revision_body);
}
}

View file

@ -0,0 +1,141 @@
<?php
namespace Drupal\Tests\content_moderation\Functional;
use Drupal\Core\Url;
use Drupal\node\Entity\Node;
/**
* Tests general content moderation workflow for nodes.
*
* @group content_moderation
*/
class ModerationStateNodeTest 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 creating and deleting content.
*/
public function testCreatingContent() {
$this->drupalPostForm('node/add/moderated_content', [
'title[0][value]' => 'moderated content',
], t('Save and Create New Draft'));
$node = $this->getNodeByTitle('moderated content');
if (!$node) {
$this->fail('Test node was not saved correctly.');
}
$this->assertEqual('draft', $node->moderation_state->value);
$path = 'node/' . $node->id() . '/edit';
// Set up published revision.
$this->drupalPostForm($path, [], t('Save and Publish'));
\Drupal::entityTypeManager()->getStorage('node')->resetCache([$node->id()]);
/* @var \Drupal\node\NodeInterface $node */
$node = \Drupal::entityTypeManager()->getStorage('node')->load($node->id());
$this->assertTrue($node->isPublished());
$this->assertEqual('published', $node->moderation_state->value);
// Verify that the state field is not shown.
$this->assertNoText('Published');
// Delete the node.
$this->drupalPostForm('node/' . $node->id() . '/delete', [], t('Delete'));
$this->assertText(t('The Moderated content moderated content has been deleted.'));
// Disable content moderation.
$this->drupalPostForm('admin/structure/types/manage/moderated_content/moderation', ['workflow' => ''], t('Save'));
$this->drupalGet('admin/structure/types/manage/moderated_content/moderation');
$this->assertOptionSelected('edit-workflow', '');
// 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();
// Create a new node.
$this->drupalPostForm('node/add/moderated_content', [
'title[0][value]' => 'non-moderated content',
], t('Save and publish'));
$node = $this->getNodeByTitle('non-moderated content');
if (!$node) {
$this->fail('Non-moderated test node was not saved correctly.');
}
$this->assertEqual(NULL, $node->moderation_state->value);
}
/**
* Tests edit form destinations.
*/
public function testFormSaveDestination() {
// 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');
$edit_path = sprintf('node/%d/edit', $node->id());
// After saving, we should be at the canonical URL and viewing the first
// revision.
$this->assertUrl(Url::fromRoute('entity.node.canonical', ['node' => $node->id()]));
$this->assertText('First version of the content.');
// Create a new draft; after saving, we should still be on the canonical
// URL, but viewing the second revision.
$this->drupalPostForm($edit_path, [
'body[0][value]' => 'Second version of the content.',
], t('Save and Create New Draft'));
$this->assertUrl(Url::fromRoute('entity.node.canonical', ['node' => $node->id()]));
$this->assertText('Second version of the content.');
// Make a new published revision; after saving, we should be at the
// canonical URL.
$this->drupalPostForm($edit_path, [
'body[0][value]' => 'Third version of the content.',
], t('Save and Publish'));
$this->assertUrl(Url::fromRoute('entity.node.canonical', ['node' => $node->id()]));
$this->assertText('Third version of the content.');
// Make a new forward revision; after saving, we should be on the "Latest
// version" tab.
$this->drupalPostForm($edit_path, [
'body[0][value]' => 'Fourth version of the content.',
], t('Save and Create New Draft'));
$this->assertUrl(Url::fromRoute('entity.node.latest_version', ['node' => $node->id()]));
$this->assertText('Fourth version of the content.');
}
/**
* Tests pagers aren't broken by content_moderation.
*/
public function testPagers() {
// Create 51 nodes to force the pager.
foreach (range(1, 51) as $delta) {
Node::create([
'type' => 'moderated_content',
'uid' => $this->adminUser->id(),
'title' => 'Node ' . $delta,
'status' => 1,
'moderation_state' => 'published',
])->save();
}
$this->drupalLogin($this->adminUser);
$this->drupalGet('admin/content');
$element = $this->cssSelect('nav.pager li.is-active a');
$url = $element[0]->getAttribute('href');
$query = [];
parse_str(parse_url($url, PHP_URL_QUERY), $query);
$this->assertEqual(0, $query['page']);
}
}

View file

@ -0,0 +1,94 @@
<?php
namespace Drupal\Tests\content_moderation\Functional;
/**
* Tests moderation state node type integration.
*
* @group content_moderation
*/
class ModerationStateNodeTypeTest extends ModerationStateTestBase {
/**
* A node type without moderation state disabled.
*/
public function testNotModerated() {
$this->drupalLogin($this->adminUser);
$this->createContentTypeFromUi('Not moderated', 'not_moderated');
$this->assertText('The content type Not moderated has been added.');
$this->grantUserPermissionToCreateContentOfType($this->adminUser, 'not_moderated');
$this->drupalGet('node/add/not_moderated');
$this->assertRaw('Save as unpublished');
$this->drupalPostForm(NULL, [
'title[0][value]' => 'Test',
], t('Save and publish'));
$this->assertText('Not moderated Test has been created.');
}
/**
* 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($editor);
$this->createContentTypeFromUi('Not moderated', 'not_moderated');
$this->grantUserPermissionToCreateContentOfType($editor, 'not_moderated');
$this->grantUserPermissionToCreateContentOfType($editor_with_publish, 'not_moderated');
// Create content.
$this->drupalGet('node/add/not_moderated');
$this->drupalPostForm(NULL, [
'title[0][value]' => 'Test',
], t('Save and publish'));
$this->assertText('Not moderated Test has been created.');
// Now enable moderation state, ensuring all the expected links and tabs are
// present.
$this->drupalGet('admin/structure/types');
$this->assertLinkByHref('admin/structure/types/manage/not_moderated/moderation');
$this->drupalGet('admin/structure/types/manage/not_moderated');
$this->assertLinkByHref('admin/structure/types/manage/not_moderated/moderation');
$this->drupalGet('admin/structure/types/manage/not_moderated/moderation');
$this->assertOptionSelected('edit-workflow', '');
$this->assertNoLink('Delete');
$edit['workflow'] = 'editorial';
$this->drupalPostForm(NULL, $edit, t('Save'));
// And make sure it works.
$nodes = \Drupal::entityTypeManager()->getStorage('node')
->loadByProperties(['title' => 'Test']);
if (empty($nodes)) {
$this->fail('Could not load node with title Test');
return;
}
$node = reset($nodes);
$this->drupalGet('node/' . $node->id());
$this->assertResponse(200);
$this->assertLinkByHref('node/' . $node->id() . '/edit');
$this->drupalGet('node/' . $node->id() . '/edit');
$this->assertResponse(200);
$this->assertRaw('Save and Create New Draft');
$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);
}
}

View file

@ -64,7 +64,7 @@ class ModerationStateFieldItemListTest extends KernelTestBase {
* Test the field item list when accessing an index.
*/
public function testArrayIndex() {
$this->assertEquals('draft', $this->testNode->moderation_state[0]->value);
$this->assertEquals('published', $this->testNode->moderation_state[0]->value);
}
/**
@ -75,7 +75,7 @@ class ModerationStateFieldItemListTest extends KernelTestBase {
foreach ($this->testNode->moderation_state as $item) {
$states[] = $item->value;
}
$this->assertEquals(['draft'], $states);
$this->assertEquals(['published'], $states);
}
}

View file

@ -5,10 +5,16 @@ namespace Drupal\Tests\content_moderation\Unit;
use Drupal\block_content\Entity\BlockContent;
use Drupal\Core\Access\AccessResultAllowed;
use Drupal\Core\Access\AccessResultForbidden;
use Drupal\Core\Access\AccessResultNeutral;
use Drupal\Core\Cache\Context\CacheContextsManager;
use Drupal\Core\Routing\RouteMatch;
use Drupal\Core\Session\AccountInterface;
use Drupal\node\Entity\Node;
use Drupal\content_moderation\Access\LatestRevisionCheck;
use Drupal\content_moderation\ModerationInformation;
use Drupal\user\EntityOwnerInterface;
use Prophecy\Argument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Routing\Route;
/**
@ -17,6 +23,20 @@ use Symfony\Component\Routing\Route;
*/
class LatestRevisionCheckTest extends \PHPUnit_Framework_TestCase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Initialize Drupal container since the cache context manager is needed.
$contexts_manager = $this->prophesize(CacheContextsManager::class);
$contexts_manager->assertValidTokens(Argument::any())->willReturn(TRUE);
$builder = new ContainerBuilder();
$builder->set('cache_contexts_manager', $contexts_manager->reveal());
\Drupal::setContainer($builder);
}
/**
* Test the access check of the LatestRevisionCheck service.
*
@ -26,19 +46,38 @@ class LatestRevisionCheckTest extends \PHPUnit_Framework_TestCase {
* The machine name of the entity to mock.
* @param bool $has_forward
* Whether this entity should have a forward revision in the system.
* @param array $account_permissions
* An array of permissions the account has.
* @param bool $is_owner
* Indicates if the user should be the owner of the entity.
* @param string $result_class
* The AccessResult class that should result. One of AccessResultAllowed,
* AccessResultForbidden, AccessResultNeutral.
*
* @dataProvider accessSituationProvider
*/
public function testLatestAccessPermissions($entity_class, $entity_type, $has_forward, $result_class) {
public function testLatestAccessPermissions($entity_class, $entity_type, $has_forward, array $account_permissions, $is_owner, $result_class) {
/** @var \Drupal\Core\Session\AccountInterface $account */
$account = $this->prophesize(AccountInterface::class);
$possible_permissions = [
'view latest version',
'view any unpublished content',
'view own unpublished content',
];
foreach ($possible_permissions as $permission) {
$account->hasPermission($permission)->willReturn(in_array($permission, $account_permissions));
}
$account->id()->willReturn(42);
/** @var \Drupal\Core\Entity\EntityInterface $entity */
$entity = $this->prophesize($entity_class);
$entity->getCacheContexts()->willReturn([]);
$entity->getCacheTags()->willReturn([]);
$entity->getCacheMaxAge()->willReturn(0);
if (is_subclass_of($entity_class, EntityOwnerInterface::class)) {
$entity->getOwnerId()->willReturn($is_owner ? 42 : 3);
}
/** @var \Drupal\content_moderation\ModerationInformation $mod_info */
$mod_info = $this->prophesize(ModerationInformation::class);
@ -54,7 +93,7 @@ class LatestRevisionCheckTest extends \PHPUnit_Framework_TestCase {
$lrc = new LatestRevisionCheck($mod_info->reveal());
/** @var \Drupal\Core\Access\AccessResult $result */
$result = $lrc->access($route->reveal(), $route_match->reveal());
$result = $lrc->access($route->reveal(), $route_match->reveal(), $account->reveal());
$this->assertInstanceOf($result_class, $result);
@ -65,10 +104,28 @@ class LatestRevisionCheckTest extends \PHPUnit_Framework_TestCase {
*/
public function accessSituationProvider() {
return [
[Node::class, 'node', TRUE, AccessResultAllowed::class],
[Node::class, 'node', FALSE, AccessResultForbidden::class],
[BlockContent::class, 'block_content', TRUE, AccessResultAllowed::class],
[BlockContent::class, 'block_content', FALSE, AccessResultForbidden::class],
// Node with global permissions and latest version.
[Node::class, 'node', TRUE, ['view latest version', 'view any unpublished content'], FALSE, AccessResultAllowed::class],
// Node with global permissions and no latest version.
[Node::class, 'node', FALSE, ['view latest version', 'view any unpublished content'], FALSE, AccessResultForbidden::class],
// Node with own content permissions and latest version.
[Node::class, 'node', TRUE, ['view latest version', 'view own unpublished content'], TRUE, AccessResultAllowed::class],
// Node with own content permissions and no latest version.
[Node::class, 'node', FALSE, ['view latest version', 'view own unpublished content'], FALSE, AccessResultForbidden::class],
// Node with own content permissions and latest version, but no perms to
// view latest version.
[Node::class, 'node', TRUE, ['view own unpublished content'], TRUE, AccessResultNeutral::class],
// Node with own content permissions and no latest version, but no perms
// to view latest version.
[Node::class, 'node', TRUE, ['view own unpublished content'], FALSE, AccessResultNeutral::class],
// Block with forward revision, and permissions to view any.
[BlockContent::class, 'block_content', TRUE, ['view latest version', 'view any unpublished content'], FALSE, AccessResultAllowed::class],
// Block with no forward revision.
[BlockContent::class, 'block_content', FALSE, ['view latest version', 'view any unpublished content'], FALSE, AccessResultForbidden::class],
// Block with forward revision, but no permission to view any.
[BlockContent::class, 'block_content', TRUE, ['view latest version', 'view own unpublished content'], FALSE, AccessResultNeutral::class],
// Block with no forward revision.
[BlockContent::class, 'block_content', FALSE, ['view latest version', 'view own unpublished content'], FALSE, AccessResultForbidden::class],
];
}