update drupal

This commit is contained in:
Tessier
2020-10-15 11:59:37 +02:00
parent ebb5db14b5
commit d8b9162932
558 changed files with 5954 additions and 4475 deletions
@@ -197,11 +197,8 @@ abstract class AggregatorTestBase extends BrowserTestBase {
$this->clickLink('Update items');
// Ensure we have the right number of items.
$iids = \Drupal::entityQuery('aggregator_item')->condition('fid', $feed->id())->execute();
$feed->items = [];
foreach ($iids as $iid) {
$feed->items[] = $iid;
}
$item_ids = \Drupal::entityQuery('aggregator_item')->condition('fid', $feed->id())->execute();
$feed->items = array_values($item_ids);
if ($expected_count !== NULL) {
$feed->item_count = count($feed->items);
@@ -64,16 +64,16 @@ class FeedParserTest extends AggregatorTestBase {
$this->assertText('Atom-Powered Robots Run Amok');
$this->assertLinkByHref('http://example.org/2003/12/13/atom03');
$this->assertText('Some text.');
$iids = \Drupal::entityQuery('aggregator_item')->condition('link', 'http://example.org/2003/12/13/atom03')->execute();
$item = Item::load(array_values($iids)[0]);
$item_ids = \Drupal::entityQuery('aggregator_item')->condition('link', 'http://example.org/2003/12/13/atom03')->execute();
$item = Item::load(array_values($item_ids)[0]);
$this->assertEqual('urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a', $item->getGuid(), 'Atom entry id element is parsed correctly.');
// Check for second feed entry.
$this->assertText('We tried to stop them, but we failed.');
$this->assertLinkByHref('http://example.org/2003/12/14/atom03');
$this->assertText('Some other text.');
$iids = \Drupal::entityQuery('aggregator_item')->condition('link', 'http://example.org/2003/12/14/atom03')->execute();
$item = Item::load(array_values($iids)[0]);
$item_ids = \Drupal::entityQuery('aggregator_item')->condition('link', 'http://example.org/2003/12/14/atom03')->execute();
$item = Item::load(array_values($item_ids)[0]);
$this->assertEqual('urn:uuid:1225c695-cfb8-4ebb-bbbb-80da344efa6a', $item->getGuid(), 'Atom entry id element is parsed correctly.');
}
@@ -54,8 +54,8 @@ class UpdateFeedItemTest extends AggregatorTestBase {
$feed = Feed::load(array_values($fids)[0]);
$feed->refreshItems();
$iids = \Drupal::entityQuery('aggregator_item')->condition('fid', $feed->id())->execute();
$before = Item::load(array_values($iids)[0])->getPostedTime();
$item_ids = \Drupal::entityQuery('aggregator_item')->condition('fid', $feed->id())->execute();
$before = Item::load(array_values($item_ids)[0])->getPostedTime();
// Sleep for 3 second.
sleep(3);
@@ -67,7 +67,7 @@ class UpdateFeedItemTest extends AggregatorTestBase {
->save();
$feed->refreshItems();
$after = Item::load(array_values($iids)[0])->getPostedTime();
$after = Item::load(array_values($item_ids)[0])->getPostedTime();
$this->assertTrue($before === $after, new FormattableMarkup('Publish timestamp of feed item was not updated (@before === @after)', ['@before' => $before, '@after' => $after]));
// Make sure updating items works even after uninstalling a module
@@ -39,7 +39,7 @@ class IpAddressBlockingTest extends BrowserTestBase {
$edit = [];
$edit['ip'] = '1.2.3.3';
$this->drupalPostForm('admin/config/people/ban', $edit, t('Add'));
$ip = $connection->query("SELECT iid from {ban_ip} WHERE ip = :ip", [':ip' => $edit['ip']])->fetchField();
$ip = $connection->select('ban_ip', 'bi')->fields('bi', ['iid'])->condition('ip', $edit['ip'])->execute()->fetchField();
$this->assertNotEmpty($ip, 'IP address found in database.');
$this->assertRaw(t('The IP address %ip has been banned.', ['%ip' => $edit['ip']]), 'IP address was banned.');
@@ -70,7 +70,7 @@ class IpAddressBlockingTest extends BrowserTestBase {
// Pass an IP address as a URL parameter and submit it.
$submit_ip = '1.2.3.4';
$this->drupalPostForm('admin/config/people/ban/' . $submit_ip, [], t('Add'));
$ip = $connection->query("SELECT iid from {ban_ip} WHERE ip = :ip", [':ip' => $submit_ip])->fetchField();
$ip = $connection->select('ban_ip', 'bi')->fields('bi', ['iid'])->condition('ip', $submit_ip)->execute()->fetchField();
$this->assertNotEmpty($ip, 'IP address found in database');
$this->assertRaw(t('The IP address %ip has been banned.', ['%ip' => $submit_ip]), 'IP address was banned.');
@@ -210,7 +210,7 @@ class BigPipeStrategy implements PlaceholderStrategyInterface {
'library' => [
'big_pipe/big_pipe',
],
// Inform BigPipe' JavaScript known BigPipe placeholder IDs (a whitelist).
// Inform BigPipe' JavaScript known BigPipe placeholder IDs.
'drupalSettings' => [
'bigPipePlaceholderIds' => [$big_pipe_placeholder_id => TRUE],
],
@@ -153,7 +153,7 @@ class BigPipeTest extends BrowserTestBase {
$this->assertBigPipeNoJsCookieExists(FALSE);
$connection = Database::getConnection();
$log_count = $connection->query('SELECT COUNT(*) FROM {watchdog}')->fetchField();
$log_count = $connection->select('watchdog')->countQuery()->execute()->fetchField();
// By not calling performMetaRefresh() here, we simulate JavaScript being
// enabled, because as far as the BigPipe module is concerned, JavaScript is
@@ -186,15 +186,16 @@ class BigPipeTest extends BrowserTestBase {
$this->assertRaw('</body>', 'Closing body tag present.');
$this->pass('Verifying BigPipe assets are present…', 'Debug');
// Verifying BigPipe assets are present.
$this->assertFalse(empty($this->getDrupalSettings()), 'drupalSettings present.');
$this->assertContains('big_pipe/big_pipe', explode(',', $this->getDrupalSettings()['ajaxPageState']['libraries']), 'BigPipe asset library is present.');
// Verify that the two expected exceptions are logged as errors.
$this->assertEqual($log_count + 2, $connection->query('SELECT COUNT(*) FROM {watchdog}')->fetchField(), 'Two new watchdog entries.');
// Using the method queryRange() allows contrib database drivers the ability
// to insert their own limit and offset functionality.
$records = $connection->queryRange('SELECT * FROM {watchdog} ORDER BY wid DESC', 0, 2)->fetchAll();
$this->assertEqual($log_count + 2, (int) $connection->select('watchdog')->countQuery()->execute()->fetchField(), 'Two new watchdog entries.');
// Using dynamic select queries with the method range() allows contrib
// database drivers the ability to insert their own limit and offset
// functionality.
$records = $connection->select('watchdog', 'w')->fields('w')->orderBy('wid', 'DESC')->range(0, 2)->execute()->fetchAll();
$this->assertEqual(RfcLogLevel::ERROR, $records[0]->severity);
$this->assertStringContainsString('Oh noes!', (string) unserialize($records[0]->variables)['@message']);
$this->assertEqual(RfcLogLevel::ERROR, $records[1]->severity);
@@ -205,7 +206,8 @@ class BigPipeTest extends BrowserTestBase {
$this->drupalGet(Url::fromUri('base:non-existing-path'));
// Simulate development.
$this->pass('Verifying BigPipe provides useful error output when an error occurs while rendering a placeholder if verbose error logging is enabled.', 'Debug');
// Verifying BigPipe provides useful error output when an error occurs
// while rendering a placeholder if verbose error logging is enabled.
$this->config('system.logging')->set('error_level', ERROR_REPORTING_DISPLAY_VERBOSE)->save();
$this->drupalGet(Url::fromRoute('big_pipe_test'));
// The 'edge_case__html_exception' case throws an exception.
@@ -259,13 +261,13 @@ class BigPipeTest extends BrowserTestBase {
$cases['exception__embedded_response']->bigPipePlaceholderId => NULL,
]);
$this->pass('Verifying there are no BigPipe placeholders & replacements…', 'Debug');
// Verifying there are no BigPipe placeholders & replacements.
$this->assertEqual('<none>', $this->drupalGetHeader('BigPipe-Test-Placeholders'));
$this->pass('Verifying BigPipe start/stop signals are absent…', 'Debug');
// Verifying BigPipe start/stop signals are absent.
$this->assertNoRaw(BigPipe::START_SIGNAL, 'BigPipe start signal absent.');
$this->assertNoRaw(BigPipe::STOP_SIGNAL, 'BigPipe stop signal absent.');
$this->pass('Verifying BigPipe assets are absent…', 'Debug');
// Verifying BigPipe assets are absent.
$this->assertTrue(!isset($this->getDrupalSettings()['bigPipePlaceholderIds']) && empty($this->getDrupalSettings()['ajaxPageState']), 'BigPipe drupalSettings and BigPipe asset library absent.');
$this->assertRaw('</body>', 'Closing body tag present.');
@@ -274,7 +276,8 @@ class BigPipeTest extends BrowserTestBase {
$this->drupalGet(Url::fromUri('base:non-existing-path'));
// Simulate development.
$this->pass('Verifying BigPipe provides useful error output when an error occurs while rendering a placeholder if verbose error logging is enabled.', 'Debug');
// Verifying BigPipe provides useful error output when an error occurs
// while rendering a placeholder if verbose error logging is enabled.
$this->config('system.logging')->set('error_level', ERROR_REPORTING_DISPLAY_VERBOSE)->save();
$this->drupalGet(Url::fromRoute('big_pipe_test'));
// The 'edge_case__html_exception' case throws an exception.
@@ -322,7 +325,6 @@ class BigPipeTest extends BrowserTestBase {
}
protected function assertBigPipeResponseHeadersPresent() {
$this->pass('Verifying BigPipe response headers…', 'Debug');
// Check that Cache-Control header set to "private".
$this->assertSession()->responseHeaderContains('Cache-Control', 'private');
$this->assertEqual('no-store, content="BigPipe/1.0"', $this->drupalGetHeader('Surrogate-Control'));
@@ -337,10 +339,10 @@ class BigPipeTest extends BrowserTestBase {
* markup.
*/
protected function assertBigPipeNoJsPlaceholders(array $expected_big_pipe_nojs_placeholders) {
$this->pass('Verifying BigPipe no-JS placeholders & replacements…', 'Debug');
$this->assertSetsEqual(array_keys($expected_big_pipe_nojs_placeholders), array_map('rawurldecode', explode(' ', $this->drupalGetHeader('BigPipe-Test-No-Js-Placeholders'))));
foreach ($expected_big_pipe_nojs_placeholders as $big_pipe_nojs_placeholder => $expected_replacement) {
$this->pass('Checking whether the replacement for the BigPipe no-JS placeholder "' . $big_pipe_nojs_placeholder . '" is present:');
// Checking whether the replacement for the BigPipe no-JS placeholder
// $big_pipe_nojs_placeholder is present.
$this->assertNoRaw($big_pipe_nojs_placeholder);
if ($expected_replacement !== NULL) {
$this->assertRaw($expected_replacement);
@@ -358,12 +360,10 @@ class BigPipeTest extends BrowserTestBase {
* defined in the order that they are expected to be rendered & streamed.
*/
protected function assertBigPipePlaceholders(array $expected_big_pipe_placeholders, array $expected_big_pipe_placeholder_stream_order) {
$this->pass('Verifying BigPipe placeholders & replacements…', 'Debug');
$this->assertSetsEqual(array_keys($expected_big_pipe_placeholders), explode(' ', $this->drupalGetHeader('BigPipe-Test-Placeholders')));
$placeholder_positions = [];
$placeholder_replacement_positions = [];
foreach ($expected_big_pipe_placeholders as $big_pipe_placeholder_id => $expected_ajax_response) {
$this->pass('BigPipe placeholder: ' . $big_pipe_placeholder_id, 'Debug');
// Verify expected placeholder.
$expected_placeholder_html = '<span data-big-pipe-placeholder-id="' . $big_pipe_placeholder_id . '"></span>';
$this->assertRaw($expected_placeholder_html, 'BigPipe placeholder for placeholder ID "' . $big_pipe_placeholder_id . '" found.');
@@ -396,14 +396,15 @@ class BigPipeTest extends BrowserTestBase {
$this->assertSetsEqual(array_keys($expected_big_pipe_placeholders_with_replacements), array_values($placeholder_replacement_positions));
$this->assertEqual(count($expected_big_pipe_placeholders_with_replacements), preg_match_all('/' . preg_quote('<script type="application/vnd.drupal-ajax" data-big-pipe-replacement-for-placeholder-with-id="', '/') . '/', $this->getSession()->getPage()->getContent()));
$this->pass('Verifying BigPipe start/stop signals…', 'Debug');
// Verifying BigPipe start/stop signals.
$this->assertRaw(BigPipe::START_SIGNAL, 'BigPipe start signal present.');
$this->assertRaw(BigPipe::STOP_SIGNAL, 'BigPipe stop signal present.');
$start_signal_position = strpos($this->getSession()->getPage()->getContent(), BigPipe::START_SIGNAL);
$stop_signal_position = strpos($this->getSession()->getPage()->getContent(), BigPipe::STOP_SIGNAL);
$this->assertTrue($start_signal_position < $stop_signal_position, 'BigPipe start signal appears before stop signal.');
$this->pass('Verifying BigPipe placeholder replacements and start/stop signals were streamed in the correct order…', 'Debug');
// Verifying BigPipe placeholder replacements and start/stop signals were
// streamed in the correct order.
$expected_stream_order = array_keys($expected_big_pipe_placeholders_with_replacements);
array_unshift($expected_stream_order, BigPipe::START_SIGNAL);
array_push($expected_stream_order, BigPipe::STOP_SIGNAL);
@@ -292,7 +292,6 @@ class BlockViewBuilderTest extends KernelTestBase {
// Check that the expected cacheability metadata is present in:
// - the built render array;
$this->pass('Built render array');
$build = $this->getBlockRenderArray();
$this->assertIdentical($expected_keys, $build['#cache']['keys']);
$this->assertIdentical($expected_contexts, $build['#cache']['contexts']);
@@ -300,10 +299,8 @@ class BlockViewBuilderTest extends KernelTestBase {
$this->assertIdentical($expected_max_age, $build['#cache']['max-age']);
$this->assertFalse(isset($build['#create_placeholder']));
// - the rendered render array;
$this->pass('Rendered render array');
$this->renderer->renderRoot($build);
// - the render cache item.
$this->pass('Render cache item');
$final_cache_contexts = Cache::mergeContexts($expected_contexts, $required_cache_contexts);
$cid = implode(':', $expected_keys) . ':' . implode(':', \Drupal::service('cache_contexts_manager')->convertTokensToKeys($final_cache_contexts)->getKeys());
$cache_item = $this->container->get('cache.render')->get($cid);
@@ -37,7 +37,7 @@ class BlockVisibilityTest extends MigrateProcessTestCase {
* @covers ::transform
*/
public function testTransformNoData() {
$transformed_value = $this->plugin->transform([0, '', []], $this->migrateExecutable, $this->row, 'destinationproperty');
$transformed_value = $this->plugin->transform([0, '', []], $this->migrateExecutable, $this->row, 'destination_property');
$this->assertEmpty($transformed_value);
}
@@ -45,7 +45,7 @@ class BlockVisibilityTest extends MigrateProcessTestCase {
* @covers ::transform
*/
public function testTransformSinglePageWithFront() {
$visibility = $this->plugin->transform([0, '<front>', []], $this->migrateExecutable, $this->row, 'destinationproperty');
$visibility = $this->plugin->transform([0, '<front>', []], $this->migrateExecutable, $this->row, 'destination_property');
$this->assertSame('request_path', $visibility['request_path']['id']);
$this->assertTrue($visibility['request_path']['negate']);
$this->assertSame('<front>', $visibility['request_path']['pages']);
@@ -55,7 +55,7 @@ class BlockVisibilityTest extends MigrateProcessTestCase {
* @covers ::transform
*/
public function testTransformMultiplePagesWithFront() {
$visibility = $this->plugin->transform([1, "foo\n/bar\rbaz\r\n<front>", []], $this->migrateExecutable, $this->row, 'destinationproperty');
$visibility = $this->plugin->transform([1, "foo\n/bar\rbaz\r\n<front>", []], $this->migrateExecutable, $this->row, 'destination_property');
$this->assertSame('request_path', $visibility['request_path']['id']);
$this->assertFalse($visibility['request_path']['negate']);
$this->assertSame("/foo\n/bar\n/baz\n<front>", $visibility['request_path']['pages']);
@@ -66,7 +66,7 @@ class BlockVisibilityTest extends MigrateProcessTestCase {
*/
public function testTransformPhpEnabled() {
$this->moduleHandler->moduleExists('php')->willReturn(TRUE);
$visibility = $this->plugin->transform([2, '<?php', []], $this->migrateExecutable, $this->row, 'destinationproperty');
$visibility = $this->plugin->transform([2, '<?php', []], $this->migrateExecutable, $this->row, 'destination_property');
$this->assertSame('php', $visibility['php']['id']);
$this->assertFalse($visibility['php']['negate']);
$this->assertSame('<?php', $visibility['php']['php']);
@@ -77,7 +77,7 @@ class BlockVisibilityTest extends MigrateProcessTestCase {
*/
public function testTransformPhpDisabled() {
$this->moduleHandler->moduleExists('php')->willReturn(FALSE);
$transformed_value = $this->plugin->transform([2, '<?php', []], $this->migrateExecutable, $this->row, 'destinationproperty');
$transformed_value = $this->plugin->transform([2, '<?php', []], $this->migrateExecutable, $this->row, 'destination_property');
$this->assertEmpty($transformed_value);
}
@@ -97,7 +97,7 @@ class BlockVisibilityTest extends MigrateProcessTestCase {
$this->plugin = new BlockVisibility(['skip_php' => TRUE], 'block_visibility_pages', [], $this->moduleHandler->reveal(), $migrate_lookup->reveal());
$this->expectException(MigrateSkipRowException::class);
$this->expectExceptionMessage("The block with bid '99' from module 'foobar' will have no PHP or request_path visibility configuration.");
$this->plugin->transform([2, '<?php', []], $this->migrateExecutable, $this->row, 'destinationproperty');
$this->plugin->transform([2, '<?php', []], $this->migrateExecutable, $this->row, 'destination_property');
}
}
@@ -44,7 +44,7 @@ class BlockCustomTranslation extends DrupalSqlBase {
// Add in the property, which is either title or body. Cast the bid to text
// so PostgreSQL can make the join.
$query->leftJoin(static::I18N_STRING_TABLE, 'i18n', 'i18n.objectid = CAST(b.bid as CHAR(255))');
$query->leftJoin(static::I18N_STRING_TABLE, 'i18n', 'i18n.objectid = CAST(b.bid AS CHAR(255))');
$query->condition('i18n.type', 'block');
// Add in the translation for the property.
@@ -204,7 +204,7 @@ class BlockContentCreationTest extends BlockContentTestBase {
$this->fail('Expected exception has not been thrown.');
}
catch (\Exception $e) {
$this->pass('Expected exception has been thrown.');
// Expected exception; just continue testing.
}
$connection = Database::getConnection();
@@ -145,12 +145,10 @@ class BlockContentTranslationUITest extends ContentTranslationUITestBase {
$entity->addTranslation('it', $values);
try {
$message = 'Blocks can have translations with the same "info" value.';
$entity->save();
$this->pass($message);
}
catch (\Exception $e) {
$this->fail($message);
$this->fail('Blocks can have translations with the same "info" value.');
}
// Check that the translate operation link is shown.
@@ -33,15 +33,10 @@ class MigrateBlockContentStubTest extends MigrateDrupalTestBase {
* Tests creation of block content stubs with no block_content_type available.
*/
public function testStubFailure() {
$message = 'Expected MigrateException thrown when no bundles exist.';
try {
$this->createEntityStub('block_content');
$this->fail($message);
}
catch (MigrateException $e) {
$this->pass($message);
$this->assertEqual('Stubbing failed, no bundles available for entity type: block_content', $e->getMessage());
}
// Expected MigrateException thrown when no bundles exist.
$this->expectException(MigrateException::class);
$this->expectExceptionMessage('Stubbing failed, no bundles available for entity type: block_content');
$this->createEntityStub('block_content');
}
/**
@@ -47,7 +47,6 @@ class BookSettingsForm extends ConfigFormBase {
'#options' => $types,
'#required' => TRUE,
];
$form['array_filter'] = ['#type' => 'value', '#value' => TRUE];
return parent::buildForm($form, $form_state);
}
@@ -56,7 +55,7 @@ class BookSettingsForm extends ConfigFormBase {
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
$child_type = $form_state->getValue('book_child_type');
$child_type = array_filter($form_state->getValue('book_child_type'));
if ($form_state->isValueEmpty(['book_allowed_types', $child_type])) {
$form_state->setErrorByName('book_child_type', $this->t('The content type for the %add-child link must be one of those selected as an allowed book outline type.', ['%add-child' => $this->t('Add child page')]));
}
@@ -76,7 +75,7 @@ class BookSettingsForm extends ConfigFormBase {
$this->config('book.settings')
// Remove unchecked types.
->set('allowed_types', $allowed_types)
->set('child_type', $form_state->getValue('book_child_type'))
->set('child_type', array_filter($form_state->getValue('book_child_type')))
->save();
parent::submitForm($form, $form_state);
@@ -95,7 +95,7 @@ trait BookTestTrait {
// Check outline structure.
if ($nodes !== NULL) {
$this->assertPattern($this->generateOutlinePattern($nodes), new FormattableMarkup('Node @number outline confirmed.', ['@number' => $number]));
$this->assertPattern($this->generateOutlinePattern($nodes));
}
else {
$this->pass(new FormattableMarkup('Node %number does not have outline.', ['%number' => $number]));
@@ -215,6 +215,11 @@
'figcaption',
);
const captionFilter = new CKEDITOR.filter(
widgetDefinition.editables.caption.allowedContent,
);
captionFilter.applyTo(caption);
// Use Drupal's data-placeholder attribute to insert a CSS-based,
// translation-ready placeholder for empty captions. Note that it
// also must to be done for new instances (see
@@ -139,6 +139,9 @@
var figure = new CKEDITOR.htmlParser.element('figure');
caption = new CKEDITOR.htmlParser.fragment.fromHtml(caption, 'figcaption');
var captionFilter = new CKEDITOR.filter(widgetDefinition.editables.caption.allowedContent);
captionFilter.applyTo(caption);
caption.attributes['data-placeholder'] = placeholderText;
element.replaceWith(figure);
@@ -122,7 +122,7 @@ class CKEditorPluginManager extends DefaultPluginManager {
$toolbar_rows = [];
$settings = $editor->getSettings();
foreach ($settings['toolbar']['rows'] as $row_number => $row) {
$toolbar_rows[] = array_reduce($settings['toolbar']['rows'][$row_number], function (&$result, $button_group) {
$toolbar_rows[] = array_reduce($settings['toolbar']['rows'][$row_number], function ($result, $button_group) {
return array_merge($result, $button_group['items']);
}, []);
}
@@ -120,8 +120,9 @@ class ColorTest extends BrowserTestBase {
$this->drupalGet('<front>');
$stylesheets = $this->config('color.theme.' . $theme)->get('stylesheets');
// Make sure the color stylesheet is included in the content.
foreach ($stylesheets as $stylesheet) {
$this->assertPattern('|' . file_url_transform_relative(file_create_url($stylesheet)) . '|', 'Make sure the color stylesheet is included in the content. (' . $theme . ')');
$this->assertPattern('|' . file_url_transform_relative(file_create_url($stylesheet)) . '|');
$stylesheet_content = implode("\n", file($stylesheet));
$this->assertStringContainsString('color: #123456', $stylesheet_content, 'Make sure the color we changed is in the color stylesheet. (' . $theme . ')');
}
+1 -2
View File
@@ -694,9 +694,8 @@ function template_preprocess_comment(&$variables) {
$variables['submitted'] = t('Submitted by @username on @datetime', ['@username' => $variables['author'], '@datetime' => $variables['created']]);
if ($comment->hasParentComment()) {
if ($comment_parent = $comment->getParentComment()) {
// Fetch and store the parent comment information for use in templates.
$comment_parent = $comment->getParentComment();
$account_parent = $comment_parent->getOwner();
$variables['parent_comment'] = $comment_parent;
$username = [
@@ -144,9 +144,9 @@ class CommentLazyBuilders implements TrustedCallbackInterface {
if (!$is_in_preview) {
/** @var \Drupal\comment\CommentInterface $entity */
$entity = $this->entityTypeManager->getStorage('comment')->load($comment_entity_id);
$commented_entity = $entity->getCommentedEntity();
$links['comment'] = $this->buildLinks($entity, $commented_entity);
if ($commented_entity = $entity->getCommentedEntity()) {
$links['comment'] = $this->buildLinks($entity, $commented_entity);
}
// Allow other modules to alter the comment links.
$hook_context = [
@@ -80,9 +80,11 @@ class CommentViewBuilder extends EntityViewBuilder {
/** @var \Drupal\comment\CommentInterface $entity */
// Store a threading field setting to use later in self::buildComponents().
$build['#comment_threaded'] = $entity->getCommentedEntity()
->getFieldDefinition($entity->getFieldName())
->getSetting('default_mode') === CommentManagerInterface::COMMENT_MODE_THREADED;
$commented_entity = $entity->getCommentedEntity();
$build['#comment_threaded'] =
is_null($commented_entity)
|| $commented_entity->getFieldDefinition($entity->getFieldName())
->getSetting('default_mode') === CommentManagerInterface::COMMENT_MODE_THREADED;
// If threading is enabled, don't render cache individual comments, but do
// keep the cacheability metadata, so it can bubble up.
if ($build['#comment_threaded']) {
@@ -140,10 +142,12 @@ class CommentViewBuilder extends EntityViewBuilder {
// Commented entities already loaded after self::getBuildDefaults().
$commented_entity = $entity->getCommentedEntity();
// Set defaults if the commented_entity does not exist.
$bundle = $commented_entity ? $commented_entity->bundle() : '';
$is_node = $commented_entity ? $commented_entity->getEntityTypeId() === 'node' : NULL;
$build[$id]['#entity'] = $entity;
$build[$id]['#theme'] = 'comment__' . $entity->getFieldName() . '__' . $commented_entity->bundle();
$build[$id]['#theme'] = 'comment__' . $entity->getFieldName() . '__' . $bundle;
$display = $displays[$entity->bundle()];
if ($display->getComponent('links')) {
$build[$id]['links'] = [
@@ -164,7 +168,7 @@ class CommentViewBuilder extends EntityViewBuilder {
$build[$id]['#attached'] = [];
}
$build[$id]['#attached']['library'][] = 'comment/drupal.comment-by-viewer';
if ($attach_history && $commented_entity->getEntityTypeId() === 'node') {
if ($attach_history && $is_node) {
$build[$id]['#attached']['library'][] = 'comment/drupal.comment-new-indicator';
// Embed the metadata for the comment "new" indicators on this node.
@@ -250,7 +250,7 @@ class CommentViewsData extends EntityViewsData {
// the same two tables is not supported.
if (\Drupal::service('comment.manager')->getFields($type)) {
$data['comment_entity_statistics']['table']['join'][$entity_type->getDataTable() ?: $entity_type->getBaseTable()] = [
'type' => 'INNER',
'type' => 'LEFT',
'left_field' => $entity_type->getKey('id'),
'field' => 'entity_id',
'extra' => [
@@ -404,7 +404,8 @@ class Comment extends ContentEntityBase implements CommentInterface {
* {@inheritdoc}
*/
public function getAuthorName() {
if ($this->get('uid')->target_id) {
// If their is a valid user id and the user entity exists return the label.
if ($this->get('uid')->target_id && $this->get('uid')->entity) {
return $this->get('uid')->entity->label();
}
return $this->get('name')->value ?: \Drupal::config('user.settings')->get('anonymous');
@@ -510,8 +511,8 @@ class Comment extends ContentEntityBase implements CommentInterface {
*/
public static function preCreate(EntityStorageInterface $storage, array &$values) {
if (empty($values['comment_type']) && !empty($values['field_name']) && !empty($values['entity_type'])) {
$field_storage = FieldStorageConfig::loadByName($values['entity_type'], $values['field_name']);
$values['comment_type'] = $field_storage->getSetting('comment_type');
$fields = \Drupal::service('entity_field.manager')->getFieldStorageDefinitions($values['entity_type']);
$values['comment_type'] = $fields[$values['field_name']]->getSetting('comment_type');
}
}
@@ -163,7 +163,7 @@ class NodeNewComments extends NumericField {
}
if ($nids) {
$result = $this->database->query("SELECT n.nid, COUNT(c.cid) as num_comments FROM {node} n INNER JOIN {comment_field_data} c ON n.nid = c.entity_id AND c.entity_type = 'node' AND c.default_langcode = 1
$result = $this->database->query("SELECT n.nid, COUNT(c.cid) AS num_comments FROM {node} n INNER JOIN {comment_field_data} c ON n.nid = c.entity_id AND c.entity_type = 'node' AND c.default_langcode = 1
LEFT JOIN {history} h ON h.nid = n.nid AND h.uid = :h_uid WHERE n.nid IN ( :nids[] )
AND c.changed > GREATEST(COALESCE(h.timestamp, :timestamp1), :timestamp2) AND c.status = :status GROUP BY n.nid", [
':status' => CommentInterface::PUBLISHED,
@@ -0,0 +1,8 @@
name: 'Comment base field test'
type: module
description: 'Test comment as a base field'
package: Testing
version: VERSION
dependencies:
- drupal:comment
- drupal:entity_test
@@ -0,0 +1,6 @@
langcode: en
status: true
id: test_comment_type
label: Test comment type
target_entity_type_id: comment_test_base_field
description: 'Test comment type.'
@@ -0,0 +1,39 @@
<?php
namespace Drupal\comment_base_field_test\Entity;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\entity_test\Entity\EntityTest;
/**
* Defines a test entity class for comment as a base field.
*
* @ContentEntityType(
* id = "comment_test_base_field",
* label = @Translation("Test comment - base field"),
* base_table = "comment_test_base_field",
* entity_keys = {
* "id" = "id",
* "uuid" = "uuid",
* "bundle" = "type"
* },
* )
*/
class CommentTestBaseField extends EntityTest {
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields = parent::baseFieldDefinitions($entity_type);
$fields['test_comment'] = BaseFieldDefinition::create('comment')
->setLabel(t('A comment field'))
->setSetting('comment_type', 'test_comment_type')
->setDefaultValue([
'status' => CommentItemInterface::OPEN,
]);
return $fields;
}
}
@@ -0,0 +1,244 @@
langcode: en
status: true
dependencies:
module:
- node
- user
id: test_comment_count
label: 'test comment count'
module: views
description: ''
tag: ''
base_table: node_field_data
base_field: nid
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: perm
options:
perm: 'access content'
cache:
type: tag
options: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: ''
query_tags: { }
exposed_form:
type: basic
options:
submit_button: Apply
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
pager:
type: mini
options:
items_per_page: 10
offset: 0
id: 0
total_pages: null
expose:
items_per_page: false
items_per_page_label: 'Items per page'
items_per_page_options: '5, 10, 25, 50'
items_per_page_options_all: false
items_per_page_options_all_label: '- All -'
offset: false
offset_label: Offset
tags:
previous: ‹‹
next: ››
style:
type: default
options:
grouping: { }
row_class: ''
default_row_class: true
uses_fields: false
row:
type: fields
options:
inline: { }
separator: ''
hide_empty: false
default_field_elements: true
fields:
title:
id: title
table: node_field_data
field: title
entity_type: node
entity_field: title
label: ''
alter:
alter_text: false
make_link: false
absolute: false
trim: false
word_boundary: false
ellipsis: false
strip_tags: false
html: false
hide_empty: false
empty_zero: false
settings:
link_to_entity: true
plugin_id: field
relationship: none
group_type: group
admin_label: ''
exclude: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_alter_empty: true
click_sort_column: value
type: string
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
comment_count:
id: comment_count
table: comment_entity_statistics
field: comment_count
relationship: none
group_type: group
admin_label: ''
label: ''
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
set_precision: false
precision: 0
decimal: .
separator: ''
format_plural: false
format_plural_string: !!binary MQNAY291bnQ=
prefix: ''
suffix: ''
plugin_id: numeric
filters:
status:
value: '1'
table: node_field_data
field: status
plugin_id: boolean
entity_type: node
entity_field: status
id: status
expose:
operator: ''
operator_limit_selection: false
operator_list: { }
group: 1
sorts:
created:
id: created
table: node_field_data
field: created
order: DESC
entity_type: node
entity_field: created
plugin_id: date
relationship: none
group_type: group
admin_label: ''
exposed: false
expose:
label: ''
granularity: second
header: { }
footer: { }
empty: { }
relationships: { }
arguments: { }
display_extenders: { }
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url.query_args
- 'user.node_grants:view'
- user.permissions
tags: { }
page_1:
display_plugin: page
id: page_1
display_title: Page
position: 1
display_options:
display_extenders: { }
path: test-comment-count
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url.query_args
- 'user.node_grants:view'
- user.permissions
tags: { }
@@ -188,7 +188,8 @@ class CommentAnonymousTest extends CommentTestBase {
'skip comment approval' => FALSE,
]);
$this->drupalGet('node/' . $this->node->id());
$this->assertPattern('@<h2[^>]*>Comments</h2>@', 'Comments were displayed.');
// Verify that the comment field title is displayed.
$this->assertPattern('@<h2[^>]*>Comments</h2>@');
$this->assertSession()->linkExists('Log in', 1, 'Link to login was found.');
$this->assertSession()->linkExists('register', 1, 'Link to register was found.');
@@ -55,7 +55,7 @@ class CommentInterfaceTest extends CommentTestBase {
// Test the comment field title is displayed when there's comments.
$this->drupalGet($this->node->toUrl());
$this->assertPattern('@<h2[^>]*>Comments</h2>@', 'Comments title is displayed.');
$this->assertPattern('@<h2[^>]*>Comments</h2>@');
// Set comments to have subject and preview to required.
$this->drupalLogout();
@@ -370,7 +370,8 @@ class CommentNonNodeTest extends BrowserTestBase {
'skip comment approval' => FALSE,
]);
$this->drupalGet('entity_test/' . $this->entity->id());
$this->assertPattern('@<h2[^>]*>Comments</h2>@', 'Comments were displayed.');
// Verify that the comment field title is displayed.
$this->assertPattern('@<h2[^>]*>Comments</h2>@');
$this->assertSession()->linkExists('Log in', 0, 'Link to login was found.');
$this->assertSession()->linkExists('register', 0, 'Link to register was found.');
$this->assertNoFieldByName('subject[0][value]', '', 'Subject field not found.');
@@ -429,7 +429,6 @@ class CommentPagerTest extends CommentTestBase {
$urls = $this->xpath($xpath, $arguments);
if (isset($urls[$index])) {
$url_target = $this->getAbsoluteUrl($urls[$index]->getAttribute('href'));
$this->pass(new FormattableMarkup('Clicked link %label (@url_target) from @url_before', ['%label' => $xpath, '@url_target' => $url_target, '@url_before' => $url_before]), 'Browser');
return $this->drupalGet($url_target);
}
$this->fail(new FormattableMarkup('Link %label does not exist on @url_before', ['%label' => $xpath, '@url_before' => $url_before]), 'Browser');
@@ -45,7 +45,8 @@ class CommentTitleTest extends CommentTestBase {
$regex = '/<article(.*?)id="comment-' . $comment->id() . '"(.*?)';
$regex .= $comment->comment_body->value . '(.*?)';
$regex .= '/s';
$this->assertPattern($regex, 'Comment is created successfully');
// Verify that the comment is created successfully.
$this->assertPattern($regex);
// Tests that markup is not generated for the comment without header.
$this->assertSession()->responseNotMatches('|<h3[^>]*></h3>|', 'Comment title H3 element not found when title is an empty string.');
}
@@ -76,7 +77,7 @@ class CommentTitleTest extends CommentTestBase {
// Confirm that the comment was created.
$this->assertTrue($this->commentExists($comment1), 'Comment #1. Comment found.');
// Tests that markup is created for comment with heading.
$this->assertPattern('|<h3[^>]*><a[^>]*>' . $subject_text . '</a></h3>|', 'Comment title is rendered in h3 when title populated.');
$this->assertPattern('|<h3[^>]*><a[^>]*>' . $subject_text . '</a></h3>|');
// Tests that the comment's title link is the permalink of the comment.
$comment_permalink = $this->cssSelect('.permalink');
$comment_permalink = $comment_permalink[0]->getAttribute('href');
@@ -186,7 +186,7 @@ class CommentTypeTest extends CommentTestBase {
$this->fail('Exception not thrown.');
}
catch (\InvalidArgumentException $e) {
$this->pass('Exception thrown if attempting to re-use comment-type from another entity type.');
// Expected exception; just continue testing.
}
// Delete the comment type.
@@ -26,7 +26,7 @@ class NodeCommentsTest extends CommentTestBase {
*
* @var array
*/
public static $testViews = ['test_new_comments'];
public static $testViews = ['test_new_comments', 'test_comment_count'];
/**
* Test the new comments field plugin.
@@ -38,4 +38,30 @@ class NodeCommentsTest extends CommentTestBase {
$this->assertCount(1, $new_comments, 'Found the number of new comments for a certain node.');
}
/**
* Test the comment count field.
*/
public function testCommentCount() {
$this->drupalGet('test-comment-count');
$this->assertSession()->statusCodeEquals(200);
$this->assertCount(2, $this->cssSelect('.views-row'));
$comment_count_with_comment = $this->cssSelect(".views-field-comment-count span:contains('1')");
$this->assertCount(1, $comment_count_with_comment);
$comment_count_without_comment = $this->cssSelect(".views-field-comment-count span:contains('0')");
$this->assertCount(1, $comment_count_without_comment);
// Create a content type with no comment field, and add a node.
$this->drupalCreateContentType(['type' => 'no_comment', 'name' => t('No comment page')]);
$this->nodeUserPosted = $this->drupalCreateNode(['type' => 'no_comment']);
$this->drupalGet('test-comment-count');
// Test that the node with no comment field is also shown.
$this->assertSession()->statusCodeEquals(200);
$this->assertCount(3, $this->cssSelect('.views-row'));
$comment_count_with_comment = $this->cssSelect(".views-field-comment-count span:contains('1')");
$this->assertCount(1, $comment_count_with_comment);
$comment_count_without_comment = $this->cssSelect(".views-field-comment-count span:contains('0')");
$this->assertCount(2, $comment_count_without_comment);
}
}
@@ -0,0 +1,64 @@
<?php
namespace Drupal\Tests\comment\Kernel;
use Drupal\comment\CommentInterface;
use Drupal\comment\Entity\Comment;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\comment_base_field_test\Entity\CommentTestBaseField;
use Drupal\Core\Language\LanguageInterface;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests that comment as a base field.
*
* @group comment
*/
class CommentBaseFieldTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = [
'system',
'user',
'comment',
'comment_base_field_test',
];
protected function setUp() {
parent::setUp();
$this->installEntitySchema('comment_test_base_field');
$this->installEntitySchema('comment');
$this->installSchema('system', ['sequences']);
$this->installEntitySchema('user');
}
/**
* Tests comment as a base field.
*/
public function testCommentBaseField() {
// Verify entity creation.
$entity = CommentTestBaseField::create([
'name' => $this->randomMachineName(),
'test_comment' => CommentItemInterface::OPEN,
]);
$entity->save();
$comment = Comment::create([
'entity_id' => $entity->id(),
'entity_type' => 'comment_test_base_field',
'field_name' => 'test_comment',
'pid' => 0,
'uid' => 0,
'status' => CommentInterface::PUBLISHED,
'subject' => $this->randomMachineName(),
'hostname' => '127.0.0.1',
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
'comment_body' => [['value' => $this->randomMachineName()]],
]);
$comment->save();
$this->assertEquals('test_comment_type', $comment->bundle());
}
}
@@ -0,0 +1,134 @@
<?php
namespace Drupal\Tests\comment\Kernel;
use Drupal\Core\Datetime\Entity\DateFormat;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\Tests\EntityViewTrait;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Tests loading and rendering orphan comments.
*
* @group comment
*/
class CommentOrphanTest extends EntityKernelTestBase {
use EntityViewTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['comment', 'node'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('date_format');
$this->installEntitySchema('comment');
$this->installSchema('comment', ['comment_entity_statistics']);
}
/**
* Test loading/deleting/rendering orphaned comments.
*
* @dataProvider providerTestOrphan
*/
public function testOrphan($property) {
DateFormat::create([
'id' => 'fallback',
'label' => 'Fallback',
'pattern' => 'Y-m-d',
])->save();
$comment_storage = $this->entityTypeManager->getStorage('comment');
$node_storage = $this->entityTypeManager->getStorage('node');
// Create a page node type.
$this->entityTypeManager->getStorage('node_type')->create([
'type' => 'page',
'name' => 'page',
])->save();
$node = $node_storage->create([
'type' => 'page',
'title' => 'test',
]);
$node->save();
// Create comment field.
$this->entityTypeManager->getStorage('field_storage_config')->create([
'type' => 'text_long',
'entity_type' => 'node',
'field_name' => 'comment',
])->save();
// Add comment field to page content.
$this->entityTypeManager->getStorage('field_config')->create([
'field_storage' => FieldStorageConfig::loadByName('node', 'comment'),
'entity_type' => 'node',
'bundle' => 'page',
'label' => 'Comment',
])->save();
// Make two comments
$comment1 = $comment_storage->create([
'field_name' => 'comment',
'comment_body' => 'test',
'entity_id' => $node->id(),
'entity_type' => 'node',
'comment_type' => 'default',
])->save();
$comment_storage->create([
'field_name' => 'comment',
'comment_body' => 'test',
'entity_id' => $node->id(),
'entity_type' => 'node',
'comment_type' => 'default',
'pid' => $comment1,
])->save();
// Render the comments.
$renderer = \Drupal::service('renderer');
$comments = $comment_storage->loadMultiple();
foreach ($comments as $comment) {
$built = $this->buildEntityView($comment, 'full', NULL);
$renderer->renderPlain($built);
}
// Make comment 2 an orphan by setting the property to an invalid value.
\Drupal::database()->update('comment_field_data')
->fields([$property => 10])
->condition('cid', 2)
->execute();
$comment_storage->resetCache();
$node_storage->resetCache();
// Render the comments with an orphan comment.
$comments = $comment_storage->loadMultiple();
foreach ($comments as $comment) {
$built = $this->buildEntityView($comment, 'full', NULL);
$renderer->renderPlain($built);
}
$node = $node_storage->load($node->id());
$built = $this->buildEntityView($node, 'full', NULL);
$renderer->renderPlain($built);
}
/**
* Provides test data for testOrphan.
*/
public function providerTestOrphan() {
return [
['entity_id'],
['uid'],
['pid'],
];
}
}
@@ -40,28 +40,23 @@ class CommentStringIdEntitiesTest extends KernelTestBase {
* Tests that comment fields cannot be added entities with non-integer IDs.
*/
public function testCommentFieldNonStringId() {
try {
$bundle = CommentType::create([
'id' => 'foo',
'label' => 'foo',
'description' => '',
'target_entity_type_id' => 'entity_test_string_id',
]);
$bundle->save();
$field_storage = FieldStorageConfig::create([
'field_name' => 'foo',
'entity_type' => 'entity_test_string_id',
'settings' => [
'comment_type' => 'entity_test_string_id',
],
'type' => 'comment',
]);
$field_storage->save();
$this->fail('Did not throw an exception as expected.');
}
catch (\UnexpectedValueException $e) {
$this->pass('Exception thrown when trying to create comment field on Entity Type with string ID.');
}
$this->expectException(\UnexpectedValueException::class);
$bundle = CommentType::create([
'id' => 'foo',
'label' => 'foo',
'description' => '',
'target_entity_type_id' => 'entity_test_string_id',
]);
$bundle->save();
$field_storage = FieldStorageConfig::create([
'field_name' => 'foo',
'entity_type' => 'entity_test_string_id',
'settings' => [
'comment_type' => 'entity_test_string_id',
],
'type' => 'comment',
]);
$field_storage->save();
}
}
@@ -77,6 +77,7 @@ class MigrateCommentFieldInstanceTest extends MigrateDrupal7TestBase {
$this->assertEntity('book', 'comment_node_book', 2, 1, 50, 0, TRUE, 1);
$this->assertEntity('forum', 'comment_forum', 2, 1, 50, 0, TRUE, 1);
$this->assertEntity('test_content_type', 'comment_node_test_content_type', 2, 1, 30, 0, TRUE, 1);
$this->assertEntity('et', 'comment_node_et', 2, 1, 50, 0, FALSE, 1);
}
}
@@ -51,6 +51,7 @@ class MigrateCommentFieldTest extends MigrateDrupal7TestBase {
$this->assertEntity('comment_node_book');
$this->assertEntity('comment_forum');
$this->assertEntity('comment_node_test_content_type');
$this->assertEntity('comment_node_et');
}
}
@@ -317,7 +317,7 @@ class ConfigSingleImportForm extends ConfirmFormBase {
}
// Validate for config entities.
if ($form_state->getValue('config_type') !== 'system.simple') {
if ($form_state->getValue('config_type') && $form_state->getValue('config_type') !== 'system.simple') {
$definition = $this->entityTypeManager->getDefinition($form_state->getValue('config_type'));
$id_key = $definition->getKey('id');
@@ -10,7 +10,7 @@
*/
function config_schema_test_config_schema_info_alter(&$definitions) {
if (\Drupal::state()->get('config_schema_test_exception_add')) {
$definitions['config_schema_test.hook_added_defintion'] = $definitions['config_schema_test.hook'];
$definitions['config_schema_test.hook_added_definition'] = $definitions['config_schema_test.hook'];
}
if (\Drupal::state()->get('config_schema_test_exception_remove')) {
unset($definitions['config_schema_test.hook']);
@@ -69,7 +69,7 @@ class ConfigEntityTest extends BrowserTestBase {
$this->fail('EntityMalformedException was thrown.');
}
catch (EntityMalformedException $e) {
$this->pass('EntityMalformedException was thrown.');
// Expected exception; just continue testing.
}
// Verify that an empty entity cannot be saved.
@@ -78,7 +78,7 @@ class ConfigEntityTest extends BrowserTestBase {
$this->fail('EntityMalformedException was thrown.');
}
catch (EntityMalformedException $e) {
$this->pass('EntityMalformedException was thrown.');
// Expected exception; just continue testing.
}
// Verify that an entity with an empty ID string is considered empty, too.
@@ -91,7 +91,7 @@ class ConfigEntityTest extends BrowserTestBase {
$this->fail('EntityMalformedException was thrown.');
}
catch (EntityMalformedException $e) {
$this->pass('EntityMalformedException was thrown.');
// Expected exception; just continue testing.
}
// Verify properties on a newly created entity.
@@ -116,7 +116,6 @@ class ConfigEntityTest extends BrowserTestBase {
// Verify that the entity can be saved.
try {
$status = $config_test->save();
$this->pass('EntityMalformedException was not thrown.');
}
catch (EntityMalformedException $e) {
$this->fail('EntityMalformedException was not thrown.');
@@ -151,9 +150,6 @@ class ConfigEntityTest extends BrowserTestBase {
]);
try {
$id_length_config_test->save();
$this->pass(new FormattableMarkup("config_test entity with ID length @length was saved.", [
'@length' => strlen($id_length_config_test->id()),
]));
}
catch (ConfigEntityIdLengthException $e) {
$this->fail($e->getMessage());
@@ -165,9 +161,6 @@ class ConfigEntityTest extends BrowserTestBase {
]);
try {
$id_length_config_test->save();
$this->pass(new FormattableMarkup("config_test entity with ID length @length was saved.", [
'@length' => strlen($id_length_config_test->id()),
]));
}
catch (ConfigEntityIdLengthException $e) {
$this->fail($e->getMessage());
@@ -185,10 +178,7 @@ class ConfigEntityTest extends BrowserTestBase {
]));
}
catch (ConfigEntityIdLengthException $e) {
$this->pass(new FormattableMarkup("config_test entity with ID length @length exceeding the maximum allowed length of @max failed to save", [
'@length' => strlen($id_length_config_test->id()),
'@max' => static::MAX_ID_LENGTH,
]));
// Expected exception; just continue testing.
}
// Ensure that creating an entity with the same id as an existing one is not
@@ -202,7 +192,7 @@ class ConfigEntityTest extends BrowserTestBase {
$this->fail('Not possible to overwrite an entity entity.');
}
catch (EntityStorageException $e) {
$this->pass('Not possible to overwrite an entity entity.');
// Expected exception; just continue testing.
}
// Verify that renaming the ID returns correct status and properties.
@@ -231,6 +231,10 @@ EOD;
$this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import'));
$this->assertText(t('Can not uninstall the Configuration module as part of a configuration synchronization through the user interface.'));
// Try to import without any values.
$this->drupalPostForm('admin/config/development/configuration/single/import', [], t('Import'));
$this->assertText('Configuration type field is required.');
$this->assertText('Paste your configuration here field is required.');
}
/**
@@ -29,38 +29,32 @@ class SchemaConfigListenerWebTest extends BrowserTestBase {
$this->drupalLogin($this->drupalCreateUser(['administer site configuration']));
// Test a non-existing schema.
$msg = 'Expected SchemaIncompleteException thrown';
try {
$this->config('config_schema_test.schemaless')->set('foo', 'bar')->save();
$this->fail($msg);
$this->fail('Expected SchemaIncompleteException thrown');
}
catch (SchemaIncompleteException $e) {
$this->pass($msg);
$this->assertEquals('No schema for config_schema_test.schemaless', $e->getMessage());
}
// Test a valid schema.
$msg = 'Unexpected SchemaIncompleteException thrown';
$config = $this->config('config_test.types')->set('int', 10);
try {
$config->save();
$this->pass($msg);
}
catch (SchemaIncompleteException $e) {
$this->fail($msg);
$this->fail('Unexpected SchemaIncompleteException thrown');
}
// Test an invalid schema.
$msg = 'Expected SchemaIncompleteException thrown';
$config = $this->config('config_test.types')
->set('foo', 'bar')
->set('array', 1);
try {
$config->save();
$this->fail($msg);
$this->fail('Expected SchemaIncompleteException thrown');
}
catch (SchemaIncompleteException $e) {
$this->pass($msg);
$this->assertEquals('Schema errors for config_test.types with the following errors: config_test.types:array variable type is integer but applied schema class is Drupal\Core\Config\Schema\Sequence, config_test.types:foo missing schema', $e->getMessage());
}
@@ -35,6 +35,10 @@ trait ModerationStateJoinViewsHandlerTrait {
'field' => 'content_entity_type_id',
'value' => $left_entity_type->id(),
],
[
'field' => 'content_entity_id',
'left_field' => $left_entity_type->getKey('id'),
],
],
];
if ($left_entity_type->isTranslatable()) {
@@ -123,6 +123,7 @@ class ModerationStateFilter extends InOperator implements DependentWithRemovalPl
$this->ensureMyTable();
$entity_type = $this->entityTypeManager->getDefinition($this->getEntityType());
$bundle_condition = NULL;
if ($entity_type->hasKey('bundle')) {
// Get a list of bundles that are being moderated by the workflows
// configured in this filter.
@@ -137,7 +138,7 @@ class ModerationStateFilter extends InOperator implements DependentWithRemovalPl
// If we have a list of moderated bundles, restrict the query to show only
// entities in those bundles.
if ($moderated_bundles) {
$entity_base_table_alias = $this->table;
$entity_base_table_alias = $this->relationship ?: $this->table;
// The bundle field of an entity type is not revisionable so we need to
// join the base table.
@@ -156,7 +157,8 @@ class ModerationStateFilter extends InOperator implements DependentWithRemovalPl
$entity_base_table_alias = $this->query->addRelationship($entity_base_table, $join, $entity_revision_base_table);
}
$this->query->addWhere($this->options['group'], "$entity_base_table_alias.{$entity_type->getKey('bundle')}", $moderated_bundles, 'IN');
$bundle_condition = new Condition('AND');
$bundle_condition->condition("$entity_base_table_alias.{$entity_type->getKey('bundle')}", $moderated_bundles, 'IN');
}
// Otherwise, force the query to return an empty result.
else {
@@ -186,7 +188,14 @@ class ModerationStateFilter extends InOperator implements DependentWithRemovalPl
$field->condition($and);
}
$this->query->addWhere($this->options['group'], $field);
if ($bundle_condition) {
// The query must match the bundle AND the workflow/state conditions.
$bundle_condition->condition($field);
$this->query->addWhere($this->options['group'], $bundle_condition);
}
else {
$this->query->addWhere($this->options['group'], $field);
}
}
/**
@@ -0,0 +1,348 @@
langcode: en
status: true
dependencies:
module:
- content_moderation
- node
- user
id: test_content_moderation_filter_via_relationship
label: test_content_moderation_filter_via_relationship
module: views
description: ''
tag: ''
base_table: users_field_data
base_field: uid
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: perm
options:
perm: 'access user profiles'
cache:
type: tag
options: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: ''
query_tags: { }
exposed_form:
type: basic
options:
submit_button: Apply
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
pager:
type: none
options:
offset: 0
style:
type: default
row:
type: fields
fields:
name:
id: name
table: users_field_data
field: name
relationship: none
group_type: group
admin_label: ''
label: ''
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: false
ellipsis: false
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: user_name
settings:
link_to_entity: false
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
entity_type: user
entity_field: name
plugin_id: field
title:
id: title
table: node_field_data
field: title
relationship: uid
group_type: group
admin_label: ''
label: ''
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: string
settings:
link_to_entity: false
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
entity_type: node
entity_field: title
plugin_id: field
moderation_state:
id: moderation_state
table: node_field_data
field: moderation_state
relationship: uid
group_type: group
admin_label: ''
label: ''
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: content_moderation_state
settings: { }
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
entity_type: node
plugin_id: moderation_state_field
filters:
moderation_state:
id: moderation_state
table: node_field_data
field: moderation_state
relationship: uid
group_type: group
admin_label: ''
operator: in
value: { }
group: 1
exposed: true
expose:
operator_id: moderation_state_op
label: 'Moderation state'
description: ''
use_operator: false
operator: moderation_state_op
identifier: moderation_state
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
anonymous: '0'
administrator: '0'
reduce: false
is_grouped: false
group_info:
label: ''
description: ''
identifier: ''
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items: { }
entity_type: node
plugin_id: moderation_state_filter
sorts:
vid:
id: vid
table: node_field_data
field: vid
relationship: uid
group_type: group
admin_label: ''
order: ASC
exposed: false
expose:
label: ''
entity_type: node
entity_field: vid
plugin_id: standard
title: test_content_moderation_filter_via_relationship
header: { }
footer: { }
empty: { }
relationships:
uid:
id: uid
table: users_field_data
field: uid
relationship: none
group_type: group
admin_label: nodes
required: true
entity_type: user
entity_field: uid
plugin_id: standard
arguments: { }
display_extenders: { }
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url
- user.permissions
tags:
- 'config:workflow_list'
page_1:
display_plugin: page
id: page_1
display_title: Page
position: 1
display_options:
display_extenders: { }
path: test-content-moderation-filter-relationship
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url
- user.permissions
tags:
- 'config:workflow_list'
@@ -0,0 +1,268 @@
langcode: en
status: true
dependencies:
module:
- content_moderation
- node
- user
id: test_content_moderation_state_filter_base_table_filter_group_or
label: test_content_moderation_state_filter_base_table_filter_group_or
module: views
description: ''
tag: ''
base_table: node_field_data
base_field: nid
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: perm
options:
perm: 'access content'
cache:
type: tag
options: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: ''
query_tags: { }
exposed_form:
type: basic
options:
submit_button: Apply
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
pager:
type: none
options:
offset: 0
style:
type: default
options:
grouping: { }
row_class: ''
default_row_class: true
uses_fields: false
row:
type: fields
options:
inline: { }
separator: ''
hide_empty: false
default_field_elements: true
fields:
nid:
id: nid
table: node_field_data
field: nid
relationship: none
group_type: group
admin_label: ''
label: ''
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: number_integer
settings:
thousand_separator: ''
prefix_suffix: false
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
entity_type: node
entity_field: nid
plugin_id: field
filters:
moderation_state:
id: moderation_state
table: node_field_data
field: moderation_state
relationship: none
group_type: group
admin_label: ''
operator: in
value: { }
group: 1
exposed: true
expose:
operator_id: moderation_state_op
label: 'Default Revision State'
description: ''
use_operator: false
operator: moderation_state_op
identifier: default_revision_state
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
anonymous: '0'
administrator: '0'
reduce: false
operator_limit_selection: false
operator_list: { }
is_grouped: false
group_info:
label: ''
description: ''
identifier: ''
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items: { }
entity_type: node
plugin_id: moderation_state_filter
moderation_state_1:
id: moderation_state_1
table: node_field_data
field: moderation_state
relationship: none
group_type: group
admin_label: ''
operator: 'not empty'
value: { }
group: 2
exposed: false
expose:
operator_id: ''
label: ''
description: ''
use_operator: false
operator: ''
identifier: ''
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
reduce: false
operator_limit_selection: false
operator_list: { }
is_grouped: false
group_info:
label: ''
description: ''
identifier: ''
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items: { }
entity_type: node
plugin_id: moderation_state_filter
sorts:
nid:
id: nid
table: node_field_data
field: nid
relationship: none
group_type: group
admin_label: ''
order: ASC
exposed: false
expose:
label: ''
entity_type: node
entity_field: nid
plugin_id: standard
header: { }
footer: { }
empty: { }
relationships: { }
arguments: { }
display_extenders: { }
filter_groups:
operator: AND
groups:
1: OR
2: OR
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url
- 'user.node_grants:view'
- user.permissions
tags:
- 'config:workflow_list'
page_1:
display_plugin: page
id: page_1
display_title: Page
position: 1
display_options:
display_extenders: { }
path: filter-test-path
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url
- 'user.node_grants:view'
- user.permissions
tags:
- 'config:workflow_list'
@@ -61,7 +61,7 @@ class ContentModerationAccessTest extends KernelTestBase {
/**
* Tests access cacheability.
*/
public function testAccessCacheablity() {
public function testAccessCacheability() {
$node = $this->createNode(['type' => 'page']);
/** @var \Drupal\user\RoleInterface $authenticated */
@@ -7,6 +7,7 @@ use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\Tests\content_moderation\Traits\ContentModerationTestTrait;
use Drupal\Tests\user\Traits\UserCreationTrait;
use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
use Drupal\views\Views;
use Drupal\workflows\Entity\Workflow;
@@ -21,6 +22,7 @@ use Drupal\workflows\Entity\Workflow;
class ViewsModerationStateFilterTest extends ViewsKernelTestBase {
use ContentModerationTestTrait;
use UserCreationTrait;
/**
* {@inheritdoc}
@@ -131,18 +133,26 @@ class ViewsModerationStateFilterTest extends ViewsKernelTestBase {
$translated_forward_revision->moderation_state = 'translated_draft';
$translated_forward_revision->save();
// The three default revisions are listed when no filter is specified.
$this->assertNodesWithFilters([$node, $second_node, $third_node], []);
// Test the filter within an AND filter group (the default) and an OR filter
// group.
$base_table_views = [
'test_content_moderation_state_filter_base_table',
'test_content_moderation_state_filter_base_table_filter_group_or',
];
foreach ($base_table_views as $view_id) {
// The three default revisions are listed when no filter is specified.
$this->assertNodesWithFilters([$node, $second_node, $third_node], [], $view_id);
// The default revision of node one and three are published.
$this->assertNodesWithFilters([$node, $third_node], [
'default_revision_state' => 'editorial-published',
]);
// The default revision of node one and three are published.
$this->assertNodesWithFilters([$node, $third_node], [
'default_revision_state' => 'editorial-published',
], $view_id);
// The default revision of node two is draft.
$this->assertNodesWithFilters([$second_node], [
'default_revision_state' => 'editorial-draft',
]);
// The default revision of node two is draft.
$this->assertNodesWithFilters([$second_node], [
'default_revision_state' => 'editorial-draft',
], $view_id);
}
// Test the same three revisions on a view displaying content revisions.
// Both nodes have one draft revision.
@@ -183,6 +193,52 @@ class ViewsModerationStateFilterTest extends ViewsKernelTestBase {
$this->assertIdenticalResultset($view, [['id' => $test_entity->id()]], ['id' => 'id']);
}
/**
* Tests the moderation state filter on an entity added via a relationship.
*/
public function testModerationStateFilterOnJoinedEntity() {
$workflow = Workflow::load('editorial');
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
$workflow->save();
// Create some sample content that will satisfy a view of users with a
// relationship to an item of content.
$user = $this->createUser([], 'Test user');
$node = Node::create([
'type' => 'example',
'title' => 'Test node',
'moderation_state' => 'published',
'uid' => $user->id(),
]);
$node->save();
// When filtering by published nodes, the sample content will appear.
$view = Views::getView('test_content_moderation_filter_via_relationship');
$view->setExposedInput([
'moderation_state' => 'editorial-published',
]);
$view->execute();
$this->assertIdenticalResultset($view, [
[
'name' => 'Test user',
'title' => 'Test node',
'moderation_state' => 'published',
],
], [
'name' => 'name',
'title' => 'title',
'moderation_state' => 'moderation_state',
]);
// Filtering by the draft state will filter out the sample content.
$view = Views::getView('test_content_moderation_filter_via_relationship');
$view->setExposedInput([
'moderation_state' => 'editorial-draft',
]);
$view->execute();
$this->assertIdenticalResultset($view, [], ['name' => 'name']);
}
/**
* Tests the list of states in the filter plugin.
*/
@@ -297,8 +353,11 @@ class ViewsModerationStateFilterTest extends ViewsKernelTestBase {
$this->assertEquals('vid', $configuration['left_field']);
$this->assertEquals('content_entity_type_id', $configuration['extra'][0]['field']);
$this->assertEquals('node', $configuration['extra'][0]['value']);
$this->assertEquals('langcode', $configuration['extra'][1]['field']);
$this->assertEquals('langcode', $configuration['extra'][1]['left_field']);
$this->assertEquals('content_entity_id', $configuration['extra'][1]['field']);
$this->assertEquals('nid', $configuration['extra'][1]['left_field']);
$this->assertEquals('langcode', $configuration['extra'][2]['field']);
$this->assertEquals('langcode', $configuration['extra'][2]['left_field']);
$expected_result = [];
foreach ($nodes as $node) {
@@ -9,6 +9,7 @@ use Drupal\Core\Url;
use Drupal\content_translation\BundleTranslationSettingsInterface;
use Drupal\content_translation\ContentTranslationManager;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Entity\ContentEntityFormInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityInterface;
@@ -656,6 +657,7 @@ function content_translation_preprocess_language_content_settings_table(&$variab
* Implements hook_page_attachments().
*/
function content_translation_page_attachments(&$page) {
$cache = CacheableMetadata::createFromRenderArray($page);
$route_match = \Drupal::routeMatch();
// If the current route has no parameters, return.
@@ -673,6 +675,13 @@ function content_translation_page_attachments(&$page) {
if ($entity instanceof ContentEntityInterface && $entity->hasLinkTemplate('canonical')) {
// Current route represents a content entity. Build hreflang links.
foreach ($entity->getTranslationLanguages() as $language) {
// Skip any translation that cannot be viewed.
$translation = $entity->getTranslation($language->getId());
$access = $translation->access('view', NULL, TRUE);
$cache->addCacheableDependency($access);
if (!$access->isAllowed()) {
continue;
}
$url = $entity->toUrl('canonical')
->setOption('language', $language)
->setAbsolute()
@@ -688,6 +697,8 @@ function content_translation_page_attachments(&$page) {
}
}
// Since entity was found, no need to iterate further.
return;
break;
}
// Apply updated caching information.
$cache->applyTo($page);
}
@@ -37,7 +37,8 @@ class DateTimeSchemaTest extends DateTimeHandlerTestBase {
$view = Views::getView('test_filter_datetime');
$view->initHandlers();
$filters = $view->displayHandlers->get('default')->getOption('filters');
$filters['field_date_value']['type'] = 'Date';
$filters['field_date_value']['type'] = 'date';
$view->displayHandlers->get('default')->overrideOption('filters', $filters);
$view->save();
$this->assertConfigSchemaByName('views.view.test_filter_datetime');
@@ -64,7 +64,12 @@ class DbLogResourceTest extends ResourceTestBase {
// Write a log message to the DB.
$this->container->get('logger.channel.rest')->notice('Test message');
// Get the ID of the written message.
$id = Database::getConnection()->queryRange("SELECT wid FROM {watchdog} WHERE type = :type ORDER BY wid DESC", 0, 1, [':type' => 'rest'])
$id = Database::getConnection()->select('watchdog', 'w')
->fields('w', ['wid'])
->condition('type', 'rest')
->orderBy('wid', 'DESC')
->range(0, 1)
->execute()
->fetchField();
$this->initAuthentication();
@@ -123,7 +123,9 @@ class DbLogTest extends BrowserTestBase {
'timestamp' => REQUEST_TIME,
];
\Drupal::service('logger.dblog')->log(RfcLogLevel::NOTICE, 'Test message', $context);
$wid = Database::getConnection()->query('SELECT MAX(wid) FROM {watchdog}')->fetchField();
$query = Database::getConnection()->select('watchdog');
$query->addExpression('MAX(wid)');
$wid = $query->execute()->fetchField();
// Verify the links appear correctly.
$this->drupalGet('admin/reports/dblog/event/' . $wid);
@@ -153,7 +155,10 @@ class DbLogTest extends BrowserTestBase {
$this->drupalLogin($this->adminUser);
$wid = Database::getConnection()->query("SELECT MAX(wid) FROM {watchdog} WHERE type='access denied'")->fetchField();
$query = Database::getConnection()->select('watchdog')
->condition('type', 'access denied');
$query->addExpression('MAX(wid)');
$wid = $query->execute()->fetchField();
$this->drupalGet('admin/reports/dblog/event/' . $wid);
$table = $this->xpath("//table[@class='dblog-event']");
@@ -208,7 +213,9 @@ class DbLogTest extends BrowserTestBase {
$this->generateLogEntries(1, [
'referer' => NULL,
]);
$wid = $connection->query('SELECT MAX(wid) FROM {watchdog}')->fetchField();
$query = $connection->select('watchdog');
$query->addExpression('MAX(wid)');
$wid = $query->execute()->fetchField();
$this->drupalGet('admin/reports/dblog/event/' . $wid);
// Verify table headers are present, even though the referrer is missing.
@@ -222,7 +229,9 @@ class DbLogTest extends BrowserTestBase {
$this->generateLogEntries(1, [
'request_uri' => $request_uri,
]);
$wid = $connection->query('SELECT MAX(wid) FROM {watchdog}')->fetchField();
$query = $connection->select('watchdog');
$query->addExpression('MAX(wid)');
$wid = $query->execute()->fetchField();
$this->drupalGet('admin/reports/dblog/event/' . $wid);
// Verify table headers are present.
@@ -322,7 +331,9 @@ class DbLogTest extends BrowserTestBase {
}
// View the database log event page.
$wid = Database::getConnection()->query('SELECT MIN(wid) FROM {watchdog}')->fetchField();
$query = Database::getConnection()->select('watchdog');
$query->addExpression('MIN(wid)');
$wid = $query->execute()->fetchField();
$this->drupalGet('admin/reports/dblog/event/' . $wid);
$this->assertSession()->statusCodeEquals($response);
if ($response == 200) {
@@ -335,7 +346,9 @@ class DbLogTest extends BrowserTestBase {
*/
private function verifyBreadcrumbs() {
// View the database log event page.
$wid = Database::getConnection()->query('SELECT MIN(wid) FROM {watchdog}')->fetchField();
$query = Database::getConnection()->select('watchdog');
$query->addExpression('MIN(wid)');
$wid = $query->execute()->fetchField();
$this->drupalGet('admin/reports/dblog/event/' . $wid);
$xpath = '//nav[@class="breadcrumb"]/ol/li[last()]/a';
$this->assertEqual(current($this->xpath($xpath))->getText(), 'Recent log messages', 'DBLogs link displayed at breadcrumb in event page.');
@@ -384,7 +397,7 @@ class DbLogTest extends BrowserTestBase {
'link' => $link,
]);
$result = Database::getConnection()->queryRange('SELECT wid FROM {watchdog} ORDER BY wid DESC', 0, 1);
$result = Database::getConnection()->select('watchdog', 'w')->fields('w', ['wid'])->orderBy('wid', 'DESC')->range(0, 1)->execute();
$this->drupalGet('admin/reports/dblog/event/' . $result->fetchField());
// Check if the link exists (unescaped).
@@ -418,7 +431,7 @@ class DbLogTest extends BrowserTestBase {
// Log out user.
$this->drupalLogout();
// Fetch the row IDs in watchdog that relate to the user.
$result = Database::getConnection()->query('SELECT wid FROM {watchdog} WHERE uid = :uid', [':uid' => $user->id()]);
$result = Database::getConnection()->select('watchdog', 'w')->fields('w', ['wid'])->condition('uid', $user->id())->execute();
foreach ($result as $row) {
$ids[] = $row->wid;
}
@@ -590,7 +603,7 @@ class DbLogTest extends BrowserTestBase {
global $base_root;
$connection = Database::getConnection();
// Get a count of how many watchdog entries already exist.
$count = $connection->query('SELECT COUNT(*) FROM {watchdog}')->fetchField();
$count = $connection->select('watchdog')->countQuery()->execute()->fetchField();
$log = [
'channel' => 'system',
'message' => 'Log entry added to test the doClearTest clear down.',
@@ -606,7 +619,7 @@ class DbLogTest extends BrowserTestBase {
// Add a watchdog entry.
$this->container->get('logger.dblog')->log($log['severity'], $log['message'], $log);
// Make sure the table count has actually been incremented.
$this->assertEqual($count + 1, $connection->query('SELECT COUNT(*) FROM {watchdog}')->fetchField(), new FormattableMarkup('\Drupal\dblog\Logger\DbLog->log() added an entry to the dblog :count', [':count' => $count]));
$this->assertEqual($count + 1, (int) $connection->select('watchdog')->countQuery()->execute()->fetchField(), new FormattableMarkup('\Drupal\dblog\Logger\DbLog->log() added an entry to the dblog :count', [':count' => $count]));
// Log in the admin user.
$this->drupalLogin($this->adminUser);
// Post in order to clear the database table.
@@ -614,7 +627,7 @@ class DbLogTest extends BrowserTestBase {
// Confirm that the logs should be cleared.
$this->drupalPostForm(NULL, [], 'Confirm');
// Count the rows in watchdog that previously related to the deleted user.
$count = $connection->query('SELECT COUNT(*) FROM {watchdog}')->fetchField();
$count = $connection->select('watchdog')->countQuery()->execute()->fetchField();
$this->assertEqual($count, 0, new FormattableMarkup('DBLog contains :count records after a clear.', [':count' => $count]));
}
@@ -803,7 +816,9 @@ class DbLogTest extends BrowserTestBase {
// Generate a single watchdog entry.
$this->generateLogEntries(1, ['user' => $tempuser, 'uid' => $tempuser_uid]);
$wid = Database::getConnection()->query('SELECT MAX(wid) FROM {watchdog}')->fetchField();
$query = Database::getConnection()->select('watchdog');
$query->addExpression('MAX(wid)');
$wid = $query->execute()->fetchField();
// Check if the full message displays on the details page.
$this->drupalGet('admin/reports/dblog/event/' . $wid);
@@ -833,7 +848,9 @@ class DbLogTest extends BrowserTestBase {
// Make sure HTML tags are filtered out in admin/reports/dblog/event/ too.
$this->generateLogEntries(1, ['message' => "<script>alert('foo');</script> <strong>Lorem ipsum</strong>"]);
$wid = Database::getConnection()->query('SELECT MAX(wid) FROM {watchdog}')->fetchField();
$query = Database::getConnection()->select('watchdog');
$query->addExpression('MAX(wid)');
$wid = $query->execute()->fetchField();
$this->drupalGet('admin/reports/dblog/event/' . $wid);
$this->assertNoRaw("<script>alert('foo');</script>");
$this->assertRaw("alert('foo'); <strong>Lorem ipsum</strong>");
@@ -864,7 +881,9 @@ class DbLogTest extends BrowserTestBase {
$this->drupalLogin($this->adminUser);
$this->drupalGet('/error-test/generate-warnings');
$wid = Database::getConnection()->query('SELECT MAX(wid) FROM {watchdog}')->fetchField();
$query = Database::getConnection()->select('watchdog');
$query->addExpression('MAX(wid)');
$wid = $query->execute()->fetchField();
$this->drupalGet('admin/reports/dblog/event/' . $wid);
$error_user_notice = [
@@ -39,7 +39,10 @@ class ConnectionFailureTest extends KernelTestBase {
// Re-establish the default database connection.
$database = Database::getConnection();
$wid = $database->query("SELECT MAX(wid) FROM {watchdog} WHERE message = 'testConnectionFailureLogging'")->fetchField();
$query = $database->select('watchdog')
->condition('message', 'testConnectionFailureLogging');
$query->addExpression('MAX(wid)');
$wid = $query->execute()->fetchField();
$this->assertNotEmpty($wid, 'Watchdog entry has been stored in database.');
}
@@ -40,7 +40,7 @@ class DbLogTest extends KernelTestBase {
// Generate additional log entries.
$this->generateLogEntries($row_limit + 10);
// Verify that the database log row count exceeds the row limit.
$count = Database::getConnection()->query('SELECT COUNT(wid) FROM {watchdog}')->fetchField();
$count = Database::getConnection()->select('watchdog')->countQuery()->execute()->fetchField();
$this->assertGreaterThan($row_limit, $count, new FormattableMarkup('Dblog row count of @count exceeds row limit of @limit', ['@count' => $count, '@limit' => $row_limit]));
// Get the number of enabled modules. Cron adds a log entry for each module.
@@ -66,13 +66,17 @@ class DbLogTest extends KernelTestBase {
// Get last ID to compare against; log entries get deleted, so we can't
// reliably add the number of newly created log entries to the current count
// to measure number of log entries created by cron.
$last_id = $connection->query('SELECT MAX(wid) FROM {watchdog}')->fetchField();
$query = $connection->select('watchdog');
$query->addExpression('MAX(wid)');
$last_id = $query->execute()->fetchField();
// Run a cron job.
$this->container->get('cron')->run();
// Get last ID after cron was run.
$current_id = $connection->query('SELECT MAX(wid) FROM {watchdog}')->fetchField();
$query = $connection->select('watchdog');
$query->addExpression('MAX(wid)');
$current_id = $query->execute()->fetchField();
return $current_id - $last_id;
}
@@ -2,7 +2,6 @@
namespace Drupal\Tests\editor\Functional;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Component\Serialization\Json;
use Drupal\editor\Entity\Editor;
use Drupal\filter\Entity\FilterFormat;
@@ -282,10 +281,6 @@ class EditorSecurityTest extends BrowserTestBase {
// Log in as each user that may edit the content, and assert the value.
foreach ($expected as $case) {
foreach ($case['users'] as $account) {
$this->pass(new FormattableMarkup('Scenario: sample %sample_id, %format.', [
'%sample_id' => $case['node_id'],
'%format' => $case['format'],
]));
$this->drupalLogin($account);
$this->drupalGet('node/' . $case['node_id'] . '/edit');
$dom_node = $this->xpath('//textarea[@id="edit-body-0-value"]');
@@ -407,12 +402,6 @@ class EditorSecurityTest extends BrowserTestBase {
// Switch to every other text format/editor and verify the results.
foreach ($case['switch_to'] as $format => $expected_filtered_value) {
$this->pass(new FormattableMarkup('Scenario: sample %sample_id, switch from %original_format to %format.', [
'%sample_id' => $case['node_id'],
'%original_format' => $case['format'],
'%format' => $format,
]));
$post = [
'value' => self::$sampleContent,
'original_format_id' => $case['format'],
@@ -71,44 +71,44 @@ class EditorFileReferenceFilterTest extends KernelTestBase {
$uuid_2 = $image_2->uuid();
$cache_tag_2 = ['file:' . $id_2];
$this->pass('No data-entity-type and no data-entity-uuid attribute.');
// No data-entity-type and no data-entity-uuid attribute.
$input = '<img src="llama.jpg" />';
$output = $test($input);
$this->assertIdentical($input, $output->getProcessedText());
$this->pass('A non-file data-entity-type attribute value.');
// A non-file data-entity-type attribute value.
$input = '<img src="llama.jpg" data-entity-type="invalid-entity-type-value" data-entity-uuid="' . $uuid . '" />';
$output = $test($input);
$this->assertIdentical($input, $output->getProcessedText());
$this->pass('One data-entity-uuid attribute.');
// One data-entity-uuid attribute.
$input = '<img src="llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '" />';
$expected_output = '<img src="/' . $this->siteDirectory . '/files/llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '" />';
$output = $test($input);
$this->assertIdentical($expected_output, $output->getProcessedText());
$this->assertEqual($cache_tag, $output->getCacheTags());
$this->pass('One data-entity-uuid attribute with odd capitalization.');
// One data-entity-uuid attribute with odd capitalization.
$input = '<img src="llama.jpg" data-entity-type="file" DATA-entity-UUID = "' . $uuid . '" />';
$expected_output = '<img src="/' . $this->siteDirectory . '/files/llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '" />';
$output = $test($input);
$this->assertIdentical($expected_output, $output->getProcessedText());
$this->assertEqual($cache_tag, $output->getCacheTags());
$this->pass('One data-entity-uuid attribute on a non-image tag.');
// One data-entity-uuid attribute on a non-image tag.
$input = '<video src="llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '" />';
$expected_output = '<video src="/' . $this->siteDirectory . '/files/llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '"></video>';
$output = $test($input);
$this->assertIdentical($expected_output, $output->getProcessedText());
$this->assertEqual($cache_tag, $output->getCacheTags());
$this->pass('One data-entity-uuid attribute with an invalid value.');
// One data-entity-uuid attribute with an invalid value.
$input = '<img src="llama.jpg" data-entity-type="file" data-entity-uuid="invalid-' . $uuid . '" />';
$output = $test($input);
$this->assertIdentical($input, $output->getProcessedText());
$this->assertEqual([], $output->getCacheTags());
$this->pass('Two different data-entity-uuid attributes.');
// Two different data-entity-uuid attributes.
$input = '<img src="llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '" />';
$input .= '<img src="alpaca.jpg" data-entity-type="file" data-entity-uuid="' . $uuid_2 . '" />';
$expected_output = '<img src="/' . $this->siteDirectory . '/files/llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '" />';
@@ -117,7 +117,7 @@ class EditorFileReferenceFilterTest extends KernelTestBase {
$this->assertIdentical($expected_output, $output->getProcessedText());
$this->assertEqual(Cache::mergeTags($cache_tag, $cache_tag_2), $output->getCacheTags());
$this->pass('Two identical data-entity-uuid attributes.');
// Two identical data-entity-uuid attributes.
$input = '<img src="llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '" />';
$input .= '<img src="llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '" />';
$expected_output = '<img src="/' . $this->siteDirectory . '/files/llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '" />';
@@ -40,7 +40,7 @@ class FieldOptionTranslation extends ProcessPluginBase {
$i = 0;
foreach ($list as $allowed_value) {
// Get the key for this allowed value which may be a key|label pair
// or or just key.
// or just key.
$value = explode("|", $allowed_value);
if (isset($value[0]) && ($value[0] == $option)) {
$allowed_values = ['label' => $row->getSourceProperty('translation')];
@@ -57,6 +57,15 @@ display:
type: default
row:
type: fields
sorts:
nid:
id: nid
table: node_field_data
field: nid
order: ASC
plugin_id: field
entity_type: node
entity_field: nid
display_plugin: default
display_title: Master
id: default
@@ -19,7 +19,7 @@ class EntityReferenceAutoCreateTest extends BrowserTestBase {
use EntityReferenceTestTrait;
public static $modules = ['node', 'taxonomy'];
public static $modules = ['node', 'taxonomy', 'entity_test'];
/**
* {@inheritdoc}
@@ -237,4 +237,51 @@ class EntityReferenceAutoCreateTest extends BrowserTestBase {
// $this->assertErrorLogged($error_message);
}
/**
* Tests autocreation for an entity that has no bundles.
*/
public function testNoBundles() {
$account = $this->drupalCreateUser([
'access content',
"create $this->referencingType content",
'administer entity_test content',
]);
$this->drupalLogin($account);
$field_name = mb_strtolower($this->randomMachineName());
$handler_settings = [
'auto_create' => TRUE,
];
$this->createEntityReferenceField('node', $this->referencingType, $field_name, $this->randomString(), 'entity_test_no_bundle_with_label', 'default', $handler_settings);
\Drupal::service('entity_display.repository')
->getFormDisplay('node', $this->referencingType)
->setComponent($field_name, ['type' => 'entity_reference_autocomplete'])
->save();
$node_title = $this->randomMachineName();
$name = $this->randomMachineName();
$edit = [
$field_name . '[0][target_id]' => $name,
'title[0][value]' => $node_title,
];
$this->drupalPostForm('node/add/' . $this->referencingType, $edit, 'Save');
// Assert referenced entity was created.
$result = \Drupal::entityQuery('entity_test_no_bundle_with_label')
->condition('name', $name)
->execute();
$this->assertNotEmpty($result, 'Referenced entity was created.');
$referenced_id = key($result);
// Assert the referenced entity is associated with referencing node.
$result = \Drupal::entityQuery('node')
->condition('type', $this->referencingType)
->execute();
$this->assertCount(1, $result);
$referencing_nid = key($result);
$referencing_node = Node::load($referencing_nid);
$this->assertEqual($referenced_id, $referencing_node->$field_name->target_id, 'Newly created node is referenced from the referencing entity.');
}
}
@@ -311,7 +311,8 @@ class FormTest extends FieldTestBase {
}
ksort($pattern);
$pattern = implode('.*', array_values($pattern));
$this->assertPattern("|$pattern|s", 'Widgets are displayed in the correct order');
// Verify that the widgets are displayed in the correct order.
$this->assertPattern("|$pattern|s");
$this->assertFieldByName("{$field_name}[$delta][value]", '', "New widget is displayed");
$this->assertFieldByName("{$field_name}[$delta][_weight]", $delta, "New widget has the right weight");
$this->assertNoField("{$field_name}[" . ($delta + 1) . '][value]', 'No extraneous widget is displayed');
@@ -344,10 +344,7 @@ class BulkDeleteTest extends FieldKernelTestBase {
// bundle.
$actual_hooks = field_test_memorize();
$hooks = [];
$entities = $this->entitiesByBundles[$bundle];
foreach ($entities as $id => $entity) {
$hooks['field_test_field_delete'][] = $entity;
}
$hooks['field_test_field_delete'] = $this->entitiesByBundles[$bundle];
$this->checkHooksInvocations($hooks, $actual_hooks);
// The field still exists, deleted.
@@ -395,10 +392,7 @@ class BulkDeleteTest extends FieldKernelTestBase {
// bundle.
$actual_hooks = field_test_memorize();
$hooks = [];
$entities = $this->entitiesByBundles[$bundle];
foreach ($entities as $id => $entity) {
$hooks['field_test_field_delete'][] = $entity;
}
$hooks['field_test_field_delete'] = $this->entitiesByBundles[$bundle];
$this->checkHooksInvocations($hooks, $actual_hooks);
// The field still exists, deleted.
@@ -430,10 +424,7 @@ class BulkDeleteTest extends FieldKernelTestBase {
// Check hooks invocations (same as above, for the 2nd bundle).
$actual_hooks = field_test_memorize();
$hooks = [];
$entities = $this->entitiesByBundles[$bundle];
foreach ($entities as $id => $entity) {
$hooks['field_test_field_delete'][] = $entity;
}
$hooks['field_test_field_delete'] = $this->entitiesByBundles[$bundle];
$this->checkHooksInvocations($hooks, $actual_hooks);
// The field and the storage still exist, deleted.
@@ -90,15 +90,10 @@ class SqlContentEntityStorageSchemaColumnTest extends KernelTestBase {
// Now attempt to run automatic updates. An exception should be thrown
// since there is data in the table.
try {
$entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager();
$field_storage_definition = $entity_definition_update_manager->getFieldStorageDefinition('test', 'entity_test_rev');
$entity_definition_update_manager->updateFieldStorageDefinition($field_storage_definition);
$this->fail('Failed to detect a schema change in a field with data.');
}
catch (FieldStorageDefinitionUpdateForbiddenException $e) {
$this->pass('Detected a schema change in a field with data.');
}
$this->expectException(FieldStorageDefinitionUpdateForbiddenException::class);
$entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager();
$field_storage_definition = $entity_definition_update_manager->getFieldStorageDefinition('test', 'entity_test_rev');
$entity_definition_update_manager->updateFieldStorageDefinition($field_storage_definition);
}
}
@@ -187,8 +187,8 @@ class EntityReferenceItemTest extends FieldKernelTestBase {
$entity->field_test_taxonomy_term = ['target_id' => 'invalid', 'entity' => $term2];
$this->fail('Assigning an invalid item throws an exception.');
}
catch (\InvalidArgumentException $e) {
$this->pass('Assigning an invalid item throws an exception.');
catch (\Exception $e) {
$this->assertInstanceOf(\InvalidArgumentException::class, $e);
}
// Delete terms so we have nothing to reference and try again
@@ -103,7 +103,7 @@ class FieldCrudTest extends FieldKernelTestBase {
$this->fail('Cannot create two fields with the same field / bundle combination.');
}
catch (EntityStorageException $e) {
$this->pass('Cannot create two fields with the same field / bundle combination.');
// Expected exception; just continue testing.
}
// Check that the specified field exists.
@@ -113,7 +113,7 @@ class FieldCrudTest extends FieldKernelTestBase {
$this->fail('Cannot create a field with a non-existing storage.');
}
catch (FieldException $e) {
$this->pass('Cannot create a field with a non-existing storage.');
// Expected exception; just continue testing.
}
// TODO: test other failures.
@@ -65,46 +65,25 @@ class FieldDefinitionIntegrityTest extends KernelTestBase {
foreach ($field_type_manager->getDefinitions() as $definition) {
// Test default field widgets.
if (isset($definition['default_widget'])) {
if (in_array($definition['default_widget'], $available_field_widget_ids)) {
$this->pass(sprintf('Field type %s uses an existing field widget by default.', $definition['id']));
}
else {
$this->fail(sprintf('Field type %s uses a non-existent field widget by default: %s', $definition['id'], $definition['default_widget']));
}
$this->assertContains($definition['default_widget'], $available_field_widget_ids, sprintf('Field type %s uses a non-existent field widget by default: %s', $definition['id'], $definition['default_widget']));
}
// Test default field formatters.
if (isset($definition['default_formatter'])) {
if (in_array($definition['default_formatter'], $available_field_formatter_ids)) {
$this->pass(sprintf('Field type %s uses an existing field formatter by default.', $definition['id']));
}
else {
$this->fail(sprintf('Field type %s uses a non-existent field formatter by default: %s', $definition['id'], $definition['default_formatter']));
}
$this->assertContains($definition['default_formatter'], $available_field_formatter_ids, sprintf('Field type %s uses a non-existent field formatter by default: %s', $definition['id'], $definition['default_formatter']));
}
}
// Test the field widget plugins.
foreach ($field_widget_manager->getDefinitions() as $definition) {
$missing_field_type_ids = array_diff($definition['field_types'], $available_field_type_ids);
if ($missing_field_type_ids) {
$this->fail(sprintf('Field widget %s integrates with non-existent field types: %s', $definition['id'], implode(', ', $missing_field_type_ids)));
}
else {
$this->pass(sprintf('Field widget %s integrates with existing field types.', $definition['id']));
}
$this->assertEmpty($missing_field_type_ids, sprintf('Field widget %s integrates with non-existent field types: %s', $definition['id'], implode(', ', $missing_field_type_ids)));
}
// Test the field formatter plugins.
foreach ($field_formatter_manager->getDefinitions() as $definition) {
$missing_field_type_ids = array_diff($definition['field_types'], $available_field_type_ids);
if ($missing_field_type_ids) {
$this->fail(sprintf('Field formatter %s integrates with non-existent field types: %s', $definition['id'], implode(', ', $missing_field_type_ids)));
}
else {
$this->pass(sprintf('Field formatter %s integrates with existing field types.', $definition['id']));
}
$this->assertEmpty($missing_field_type_ids, sprintf('Field formatter %s integrates with non-existent field types: %s', $definition['id'], implode(', ', $missing_field_type_ids)));
}
}
@@ -80,8 +80,8 @@ class FieldStorageCrudTest extends FieldKernelTestBase {
FieldStorageConfig::create($field_storage_definition)->save();
$this->fail('Cannot create two fields with the same name.');
}
catch (EntityStorageException $e) {
$this->pass('Cannot create two fields with the same name.');
catch (\Exception $e) {
$this->assertInstanceOf(EntityStorageException::class, $e);
}
// Check that field type is required.
@@ -93,8 +93,8 @@ class FieldStorageCrudTest extends FieldKernelTestBase {
FieldStorageConfig::create($field_storage_definition)->save();
$this->fail('Cannot create a field with no type.');
}
catch (FieldException $e) {
$this->pass('Cannot create a field with no type.');
catch (\Exception $e) {
$this->assertInstanceOf(FieldException::class, $e);
}
// Check that field name is required.
@@ -106,8 +106,8 @@ class FieldStorageCrudTest extends FieldKernelTestBase {
FieldStorageConfig::create($field_storage_definition)->save();
$this->fail('Cannot create an unnamed field.');
}
catch (FieldException $e) {
$this->pass('Cannot create an unnamed field.');
catch (\Exception $e) {
$this->assertInstanceOf(FieldException::class, $e);
}
// Check that entity type is required.
try {
@@ -118,8 +118,8 @@ class FieldStorageCrudTest extends FieldKernelTestBase {
FieldStorageConfig::create($field_storage_definition)->save();
$this->fail('Cannot create a field without an entity type.');
}
catch (FieldException $e) {
$this->pass('Cannot create a field without an entity type.');
catch (\Exception $e) {
$this->assertInstanceOf(FieldException::class, $e);
}
// Check that field name must start with a letter or _.
@@ -132,8 +132,8 @@ class FieldStorageCrudTest extends FieldKernelTestBase {
FieldStorageConfig::create($field_storage_definition)->save();
$this->fail('Cannot create a field with a name starting with a digit.');
}
catch (FieldException $e) {
$this->pass('Cannot create a field with a name starting with a digit.');
catch (\Exception $e) {
$this->assertInstanceOf(FieldException::class, $e);
}
// Check that field name must only contain lowercase alphanumeric or _.
@@ -146,8 +146,8 @@ class FieldStorageCrudTest extends FieldKernelTestBase {
FieldStorageConfig::create($field_storage_definition)->save();
$this->fail('Cannot create a field with a name containing an illegal character.');
}
catch (FieldException $e) {
$this->pass('Cannot create a field with a name containing an illegal character.');
catch (\Exception $e) {
$this->assertInstanceOf(FieldException::class, $e);
}
// Check that field name cannot be longer than 32 characters long.
@@ -160,8 +160,8 @@ class FieldStorageCrudTest extends FieldKernelTestBase {
FieldStorageConfig::create($field_storage_definition)->save();
$this->fail('Cannot create a field with a name longer than 32 characters.');
}
catch (FieldException $e) {
$this->pass('Cannot create a field with a name longer than 32 characters.');
catch (\Exception $e) {
$this->assertInstanceOf(FieldException::class, $e);
}
// Check that field name can not be an entity key.
@@ -175,8 +175,8 @@ class FieldStorageCrudTest extends FieldKernelTestBase {
FieldStorageConfig::create($field_storage_definition)->save();
$this->fail('Cannot create a field bearing the name of an entity key.');
}
catch (FieldException $e) {
$this->pass('Cannot create a field bearing the name of an entity key.');
catch (\Exception $e) {
$this->assertInstanceOf(FieldException::class, $e);
}
}
@@ -380,8 +380,8 @@ class FieldStorageCrudTest extends FieldKernelTestBase {
$field_storage->save();
$this->fail('Cannot update a field to a different type.');
}
catch (FieldException $e) {
$this->pass('Cannot update a field to a different type.');
catch (\Exception $e) {
$this->assertInstanceOf(FieldException::class, $e);
}
}
@@ -480,7 +480,6 @@ class FieldStorageCrudTest extends FieldKernelTestBase {
$field_storage->setSetting('changeable', $field_storage->getSetting('changeable') + 1);
try {
$field_storage->save();
$this->pass('A changeable setting can be updated.');
}
catch (FieldStorageDefinitionUpdateForbiddenException $e) {
$this->fail('An unchangeable setting cannot be updated.');
@@ -490,8 +489,8 @@ class FieldStorageCrudTest extends FieldKernelTestBase {
$field_storage->save();
$this->fail('An unchangeable setting can be updated.');
}
catch (FieldStorageDefinitionUpdateForbiddenException $e) {
$this->pass('An unchangeable setting cannot be updated.');
catch (\Exception $e) {
$this->assertInstanceOf(FieldStorageDefinitionUpdateForbiddenException::class, $e);
}
}
@@ -128,6 +128,8 @@ class MigrateFieldFormatterSettingsTest extends MigrateDrupal7TestBase {
$this->assertEntity('node.blog.teaser');
$this->assertComponent('node.blog.teaser', 'body', 'text_summary_or_trimmed', 'hidden', 0);
$this->assertComponent('node.blog.default', 'field_termplain', 'entity_reference_label', 'above', 13);
$this->assertComponent('node.blog.default', 'field_termrss', 'entity_reference_label', 'above', 14);
$this->assertEntity('node.book.default');
$this->assertComponent('node.book.default', 'body', 'text_default', 'hidden', 0);
@@ -167,12 +167,8 @@ class MigrateFieldInstanceTest extends MigrateDrupal7TestBase {
$this->assertEntity('node.article.field_vocab_fixed', 'vocab_fixed', 'entity_reference', FALSE, TRUE);
$this->assertEntity('node.article.field_vocab_localize', 'vocab_localize', 'entity_reference', FALSE, FALSE);
$this->assertEntity('node.article.field_vocab_translate', 'vocab_translate', 'entity_reference', FALSE, TRUE);
}
/**
* Tests the migration of text field instances with different text processing.
*/
public function testTextFieldInstances() {
// Test migration of text field instances with different text processing.
// All text and text_long field instances using a field base that has only
// plain text instances should be migrated to string and string_long fields.
// All text_with_summary field instances using a field base that has only
@@ -142,12 +142,8 @@ class MigrateFieldTest extends MigrateDrupal7TestBase {
// have a datetime_type setting.
$field = FieldStorageConfig::load('node.field_date_with_end_time');
$this->assertNull($field->getSetting('datetime_type'));
}
/**
* Tests the migration of text fields with different text processing.
*/
public function testTextFields() {
// Test the migration of text fields with different text processing.
// All text and text_long field bases that have only plain text instances
// should be migrated to string and string_long fields.
// All text_with_summary field bases that have only plain text instances
@@ -638,14 +638,13 @@ class EntityDisplayTest extends KernelTestBase {
$this->assertTrue($form_display->get('hidden')[$field_name]);
// The correct warning message has been logged.
$arguments = ['@display' => (string) t('Entity form display'), '@id' => $form_display->id(), '@name' => $field_name];
$logged = (bool) Database::getConnection()->select('watchdog', 'w')
->fields('w', ['wid'])
$variables = Database::getConnection()->select('watchdog', 'w')
->fields('w', ['variables'])
->condition('type', 'system')
->condition('message', "@display '@id': Component '@name' was disabled because its settings depend on removed dependencies.")
->condition('variables', serialize($arguments))
->execute()
->fetchAll();
$this->assertTrue($logged);
->fetchField();
$this->assertEquals($arguments, unserialize($variables));
}
/**
+5 -4
View File
@@ -961,18 +961,19 @@ function file_save_upload($form_field_name, $validators = [], $destination = FAL
*/
function _file_save_upload_single(\SplFileInfo $file_info, $form_field_name, $validators = [], $destination = FALSE, $replace = FileSystemInterface::EXISTS_REPLACE) {
$user = \Drupal::currentUser();
$original_file_name = trim($file_info->getClientOriginalName(), '.');
// Check for file upload errors and return FALSE for this file if a lower
// level system error occurred. For a complete list of errors:
// See http://php.net/manual/features.file-upload.errors.php.
switch ($file_info->getError()) {
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
\Drupal::messenger()->addError(t('The file %file could not be saved because it exceeds %maxsize, the maximum allowed size for uploads.', ['%file' => $file_info->getFilename(), '%maxsize' => format_size(Environment::getUploadMaxSize())]));
\Drupal::messenger()->addError(t('The file %file could not be saved because it exceeds %maxsize, the maximum allowed size for uploads.', ['%file' => $original_file_name, '%maxsize' => format_size(Environment::getUploadMaxSize())]));
return FALSE;
case UPLOAD_ERR_PARTIAL:
case UPLOAD_ERR_NO_FILE:
\Drupal::messenger()->addError(t('The file %file could not be saved because the upload did not complete.', ['%file' => $file_info->getFilename()]));
\Drupal::messenger()->addError(t('The file %file could not be saved because the upload did not complete.', ['%file' => $original_file_name]));
return FALSE;
case UPLOAD_ERR_OK:
@@ -984,7 +985,7 @@ function _file_save_upload_single(\SplFileInfo $file_info, $form_field_name, $va
default:
// Unknown error
\Drupal::messenger()->addError(t('The file %file could not be saved. An unknown error has occurred.', ['%file' => $file_info->getFilename()]));
\Drupal::messenger()->addError(t('The file %file could not be saved. An unknown error has occurred.', ['%file' => $original_file_name]));
return FALSE;
}
@@ -992,7 +993,7 @@ function _file_save_upload_single(\SplFileInfo $file_info, $form_field_name, $va
$values = [
'uid' => $user->id(),
'status' => 0,
'filename' => trim($file_info->getClientOriginalName(), '.'),
'filename' => $original_file_name,
'uri' => $file_info->getRealPath(),
'filesize' => $file_info->getSize(),
];
@@ -96,6 +96,10 @@ class ManagedFile extends FormElement {
foreach ($input['fids'] as $fid) {
if ($file = File::load($fid)) {
$fids[] = $file->id();
if (!$file->access('download')) {
$force_default = TRUE;
break;
}
// Temporary files that belong to other users should never be
// allowed.
if ($file->isTemporary()) {
@@ -2,8 +2,6 @@
namespace Drupal\Tests\file\Functional;
use Drupal\Core\Entity\Plugin\Validation\Constraint\ReferenceAccessConstraint;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\file\Entity\File;
use Drupal\node\Entity\NodeType;
use Drupal\user\RoleInterface;
@@ -92,11 +90,10 @@ class FilePrivateTest extends FileFieldTestBase {
$this->drupalGet('node/' . $new_node->id() . '/edit');
$this->getSession()->getPage()->find('css', 'input[name="' . $field_name . '[0][fids]"]')->setValue($node_file->id());
$this->getSession()->getPage()->pressButton(t('Save'));
// Make sure the form submit failed - we stayed on the edit form.
$this->assertUrl('node/' . $new_node->id() . '/edit');
// Check that we got the expected constraint form error.
$constraint = new ReferenceAccessConstraint();
$this->assertRaw(new FormattableMarkup($constraint->message, ['%type' => 'file', '%id' => $node_file->id()]));
$this->assertUrl('node/' . $new_node->id());
// Make sure the submitted hidden file field is empty.
$new_node = \Drupal::entityTypeManager()->getStorage('node')->loadUnchanged($new_node->id());
$this->assertTrue($new_node->get($field_name)->isEmpty());
// Attempt to reuse the existing file when creating a new node, and confirm
// that access is still denied.
$edit = [];
@@ -107,9 +104,10 @@ class FilePrivateTest extends FileFieldTestBase {
$this->getSession()->getPage()->find('css', 'input[name="' . $field_name . '[0][fids]"]')->setValue($node_file->id());
$this->getSession()->getPage()->pressButton(t('Save'));
$new_node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
$this->assertTrue(empty($new_node), 'Node was not created.');
$this->assertUrl('node/add/' . $type_name);
$this->assertRaw(new FormattableMarkup($constraint->message, ['%type' => 'file', '%id' => $node_file->id()]));
$this->assertUrl('node/' . $new_node->id());
// Make sure the submitted hidden file field is empty.
$new_node = \Drupal::entityTypeManager()->getStorage('node')->loadUnchanged($new_node->id());
$this->assertTrue($new_node->get($field_name)->isEmpty());
// Now make file_test_file_download() return everything.
\Drupal::state()->set('file_test.allow_all', TRUE);
@@ -28,7 +28,7 @@ class FileModuleTest extends KernelTestBase {
$file_name = $this->randomMachineName();
$file_info = $this->createMock(UploadedFile::class);
$file_info->expects($this->once())->method('getError')->willReturn(UPLOAD_ERR_FORM_SIZE);
$file_info->expects($this->once())->method('getFileName')->willReturn($file_name);
$file_info->expects($this->once())->method('getClientOriginalName')->willReturn($file_name);
$this->assertFalse(\_file_save_upload_single($file_info, 'name'));
$expected_message = new TranslatableMarkup('The file %file could not be saved because it exceeds %maxsize, the maximum allowed size for uploads.', ['%file' => $file_name, '%maxsize' => format_size(Environment::getUploadMaxSize())]);
$this->assertEquals($expected_message, \Drupal::messenger()->all()['error'][0]);
@@ -117,6 +117,7 @@ class FileTest extends MigrateSqlSourceTestBase {
'filesize' => '3620',
'status' => '1',
'timestamp' => '1421727515',
'filepath' => 'sites/default/files/cube.jpeg',
],
];
// Do an automatic count.
@@ -143,6 +144,7 @@ class FileTest extends MigrateSqlSourceTestBase {
'filesize' => '3620',
'status' => '1',
'timestamp' => '1421727515',
'filepath' => 'sites/default/files/cube.jpeg',
],
];
// Do an automatic count.
@@ -31,7 +31,7 @@ class FieldFileTest extends UnitTestCase {
$options = [
'alt' => 'Foobaz',
'title' => 'Wambooli',
'title' => 'Bar',
];
$value = [
'fid' => 1,
@@ -45,7 +45,7 @@ class FieldFileTest extends UnitTestCase {
'display' => TRUE,
'description' => '',
'alt' => 'Foobaz',
'title' => 'Wambooli',
'title' => 'Bar',
];
$this->assertSame($expected, $transformed);
}
@@ -84,7 +84,7 @@ class TextFormat extends RenderElement {
// Ensure that children appear as subkeys of this element.
$element['#tree'] = TRUE;
$blacklist = [
$keys_not_to_copy = [
// Make \Drupal::formBuilder()->doBuildForm() regenerate child properties.
'#parents',
'#id',
@@ -108,7 +108,7 @@ class TextFormat extends RenderElement {
// Move this element into sub-element 'value'.
unset($element['value']);
foreach (Element::properties($element) as $key) {
if (!in_array($key, $blacklist)) {
if (!in_array($key, $keys_not_to_copy)) {
$element['value'][$key] = $element[$key];
}
}
@@ -304,7 +304,7 @@ class FilterFormat extends ConfigEntityBase implements FilterFormatInterface, En
// with the existing set, to ensure we only end up with the tags that are
// allowed by *all* filters with an "allowed html" setting.
else {
// Track the union of forbidden (blacklisted) tags.
// Track the union of forbidden tags.
if (isset($new_restrictions['forbidden_tags'])) {
if (!isset($restrictions['forbidden_tags'])) {
$restrictions['forbidden_tags'] = $new_restrictions['forbidden_tags'];
@@ -314,15 +314,15 @@ class FilterFormat extends ConfigEntityBase implements FilterFormatInterface, En
}
}
// Track the intersection of allowed (whitelisted) tags.
// Track the intersection of allowed tags.
if (isset($restrictions['allowed'])) {
$intersection = $restrictions['allowed'];
foreach ($intersection as $tag => $attributes) {
// If the current tag is not whitelisted by the new filter, then
// it's outside of the intersection.
// If the current tag is not allowed by the new filter, then it's
// outside of the intersection.
if (!array_key_exists($tag, $new_restrictions['allowed'])) {
// The exception is the asterisk (which applies to all tags): it
// does not need to be whitelisted by every filter in order to be
// does not need to be allowed by every filter in order to be
// used; not every filter needs attribute restrictions on all tags.
if ($tag === '*') {
continue;
@@ -375,10 +375,10 @@ class FilterFormat extends ConfigEntityBase implements FilterFormatInterface, En
}
}, NULL);
// Simplification: if we have both a (intersected) whitelist and a (unioned)
// blacklist, then remove any tags from the whitelist that also exist in the
// blacklist. Now the whitelist alone expresses all tag-level restrictions,
// and we can delete the blacklist.
// Simplification: if we have both allowed (intersected) and forbidden
// (unioned) tags, then remove any allowed tags that are also forbidden.
// Once complete, the list of allowed tags expresses all tag-level
// restrictions, and the list of forbidden tags can be removed.
if (isset($restrictions['allowed']) && isset($restrictions['forbidden_tags'])) {
foreach ($restrictions['forbidden_tags'] as $tag) {
if (isset($restrictions['allowed'][$tag])) {
@@ -388,9 +388,9 @@ class FilterFormat extends ConfigEntityBase implements FilterFormatInterface, En
unset($restrictions['forbidden_tags']);
}
// Simplification: if the only remaining allowed tag is the asterisk (which
// contains attribute restrictions that apply to all tags), and only
// whitelisting filters were used, then effectively nothing is allowed.
// Simplification: if the only remaining allowed tag is the asterisk
// (which contains attribute restrictions that apply to all tags), and
// there are no forbidden tags, then effectively nothing is allowed.
if (isset($restrictions['allowed'])) {
if (count($restrictions['allowed']) === 1 && array_key_exists('*', $restrictions['allowed']) && !isset($restrictions['forbidden_tags'])) {
$restrictions['allowed'] = [];
@@ -72,10 +72,10 @@ interface FilterFormatInterface extends ConfigEntityInterface {
*
* @return array|false
* A structured array as returned by FilterInterface::getHTMLRestrictions(),
* but with the intersection of all filters in this text format.
* Will either indicate blacklisting of tags or whitelisting of tags. In
* the latter case, it's possible that restrictions on attributes are also
* stored. FALSE means there are no HTML restrictions.
* but with the intersection of all filters in this text format. The
* restrictions will either forbid or allow a list of tags. In the latter
* case, it's possible that restrictions on attributes are also stored.
* FALSE means there are no HTML restrictions.
*/
public function getHtmlRestrictions();
@@ -3,10 +3,12 @@
namespace Drupal\filter\Plugin\Filter;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\filter\FilterPluginManager;
use Drupal\filter\FilterProcessResult;
use Drupal\filter\Plugin\FilterBase;
use Drupal\filter\Render\FilteredMarkup;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a filter to caption elements.
@@ -20,7 +22,43 @@ use Drupal\filter\Render\FilteredMarkup;
* type = Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_REVERSIBLE
* )
*/
class FilterCaption extends FilterBase {
class FilterCaption extends FilterBase implements ContainerFactoryPluginInterface {
/**
* Filter manager.
*
* @var \Drupal\filter\FilterPluginManager
*/
protected $filterManager;
/**
* Constructs a new FilterCaption.
*
* @param array $configuration
* Configuration.
* @param string $plugin_id
* Plugin ID.
* @param mixed $plugin_definition
* Definition.
* @param \Drupal\filter\FilterPluginManager $filter_manager
* Filter plugin manager.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, FilterPluginManager $filter_manager = NULL) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->filterManager = $filter_manager ?: \Drupal::service('plugin.manager.filter');
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('plugin.manager.filter')
);
}
/**
* {@inheritdoc}
@@ -31,6 +69,13 @@ class FilterCaption extends FilterBase {
if (stristr($text, 'data-caption') !== FALSE) {
$dom = Html::load($text);
$xpath = new \DOMXPath($dom);
$html_filter = $this->filterManager->createInstance('filter_html', [
'settings' => [
'allowed_html' => '<a href hreflang target rel> <em> <strong> <cite> <code> <br>',
'filter_html_help' => FALSE,
'filter_html_nofollow' => FALSE,
],
]);
foreach ($xpath->query('//*[@data-caption]') as $node) {
// Read the data-caption attribute's value, then delete it.
$caption = Html::escape($node->getAttribute('data-caption'));
@@ -39,10 +84,19 @@ class FilterCaption extends FilterBase {
// Sanitize caption: decode HTML encoding, limit allowed HTML tags; only
// allow inline tags that are allowed by default, plus <br>.
$caption = Html::decodeEntities($caption);
$caption = FilteredMarkup::create(Xss::filter($caption, ['a', 'em', 'strong', 'cite', 'code', 'br']));
$raw_caption = $caption;
$filtered_caption = $html_filter->process($caption, $langcode);
$result->addCacheableDependency($filtered_caption);
$caption = FilteredMarkup::create($filtered_caption->getProcessedText());
// The caption must be non-empty.
if (mb_strlen($caption) === 0) {
// The caption must be non-empty - however the Media Embed CKEditor
// plugin uses a single space to represent a newly added caption. The
// HTML filter will transform this into an empty string and prevent the
// content editor from adding a new caption. To allow for this we treat
// a raw caption value of ' ' as valid and adding the wrapping figure
// element.
// @see core/modules/media/js/plugins/drupalmedia/plugin.es6.js
if (mb_strlen($caption) === 0 && $raw_caption !== ' ') {
continue;
}
@@ -113,7 +113,7 @@ class FilterHtml extends FilterBase {
$xpath = new \DOMXPath($html_dom);
foreach ($restrictions['allowed'] as $allowed_tag => $tag_attributes) {
// By default, no attributes are allowed for a tag, but due to the
// globally whitelisted attributes, it is impossible for a tag to actually
// globally allowed attributes, it is impossible for a tag to actually
// completely disallow attributes.
if ($tag_attributes === FALSE) {
$tag_attributes = [];
@@ -149,23 +149,23 @@ class FilterHtml extends FilterBase {
}
/**
* Filter attributes on an element by name and value according to a whitelist.
* Filters attributes on an element according to a list of allowed values.
*
* @param \DOMElement $element
* The element to be processed.
* @param array $allowed_attributes
* The attributes whitelist as an array of names and values.
* The list of allowed attributes as an array of names and values.
*/
protected function filterElementAttributes(\DOMElement $element, array $allowed_attributes) {
$modified_attributes = [];
foreach ($element->attributes as $name => $attribute) {
// Remove attributes not in the whitelist.
// Remove attributes not in the list of allowed attributes.
$allowed_value = $this->findAllowedValue($allowed_attributes, $name);
if (empty($allowed_value)) {
$modified_attributes[$name] = FALSE;
}
elseif ($allowed_value !== TRUE) {
// Check the attribute values whitelist.
// Check the list of allowed attribute values.
$attribute_values = preg_split('/\s+/', $attribute->value, -1, PREG_SPLIT_NO_EMPTY);
$modified_attributes[$name] = [];
foreach ($attribute_values as $value) {
@@ -247,8 +247,8 @@ class FilterHtml extends FilterBase {
return $this->restrictions;
}
// Parse the allowed HTML setting, and gradually make the whitelist more
// specific.
// Parse the allowed HTML setting, and gradually make the list of allowed
// tags more specific.
$restrictions = ['allowed' => []];
// Make all the tags self-closing, so they will be parsed into direct
@@ -283,7 +283,7 @@ class FilterHtml extends FilterBase {
// but one allowed attribute value that some may be tempted to use
// is specifically nonsensical: the asterisk. A prefix is required for
// allowed attribute values with a wildcard. A wildcard by itself
// would mean whitelisting all possible attribute values. But in that
// would mean allowing all possible attribute values. But in that
// case, one would not specify an attribute value at all.
$allowed_attribute_values = array_filter($allowed_attribute_values, function ($value) use ($star_protector) {
return $value !== '*';
@@ -311,14 +311,14 @@ class FilterHtml extends FilterBase {
// The 'style' and 'on*' ('onClick' etc.) attributes are always forbidden,
// and are removed by Xss::filter().
// The 'lang', and 'dir' attributes apply to all elements and are always
// allowed. The value whitelist for the 'dir' attribute is enforced by
// self::filterAttributes(). Note that those two attributes are in the
// allowed. The list of allowed values for the 'dir' attribute is enforced
// by self::filterAttributes(). Note that those two attributes are in the
// short list of globally usable attributes in HTML5. They are always
// allowed since the correct values of lang and dir may only be known to
// the content author. Of the other global attributes, they are not usually
// added by hand to content, and especially the class attribute can have
// undesired visual effects by allowing content authors to apply any
// available style, so specific values should be explicitly whitelisted.
// available style, so specific values should be explicitly allowed.
// @see http://www.w3.org/TR/html5/dom.html#global-attributes
$restrictions['allowed']['*'] = [
'style' => FALSE,
@@ -234,7 +234,7 @@ class FilterID extends StaticMap implements ContainerFactoryPluginInterface {
return FilterInterface::TYPE_HTML_RESTRICTOR;
// https://www.drupal.org/project/entity_embed
case 'emtity_embed':
case 'entity_embed':
return FilterInterface::TYPE_TRANSFORM_IRREVERSIBLE;
case 'filter_align':
@@ -207,10 +207,6 @@ class FilterFormTest extends BrowserTestBase {
foreach ($found_options as $found_key => $found_option) {
$expected_key = array_search($found_option->getValue(), $expected_options);
if ($expected_key !== FALSE) {
$this->pass(new FormattableMarkup('Option @option for field @id exists.', [
'@option' => $expected_options[$expected_key],
'@id' => $id,
]));
unset($found_options[$found_key]);
unset($expected_options[$expected_key]);
}
@@ -404,8 +404,8 @@ class FilterKernelTest extends KernelTestBase {
* @todo It is possible to add script, iframe etc. to allowed tags, but this
* makes HTML filter completely ineffective.
*
* @todo Class, id, name and xmlns should be added to disallowed attributes,
* or better a whitelist approach should be used for that too.
* @todo Class, id, name and xmlns should be added to the list of forbidden
* attributes, or, better yet, use an allowed attribute list.
*/
public function testHtmlFilter() {
// Get FilterHtml object.
@@ -460,11 +460,11 @@ class FilterKernelTest extends KernelTestBase {
$f = (string) $filter->process('<br />', Language::LANGCODE_NOT_SPECIFIED);
$this->assertNormalized($f, '<br />', 'HTML filter should allow self-closing line breaks.');
// All attributes of whitelisted tags are stripped by default.
// All attributes of allowed tags are stripped by default.
$f = (string) $filter->process('<a kitten="cute" llama="awesome">link</a>', Language::LANGCODE_NOT_SPECIFIED);
$this->assertNormalized($f, '<a>link</a>', 'HTML filter should remove attributes that are not explicitly allowed.');
// Now whitelist the "llama" attribute on <a>.
// Now allow the "llama" attribute on <a>.
$filter->setConfiguration([
'settings' => [
'allowed_html' => '<a href llama> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd> <br>',
@@ -475,7 +475,7 @@ class FilterKernelTest extends KernelTestBase {
$f = (string) $filter->process('<a kitten="cute" llama="awesome">link</a>', Language::LANGCODE_NOT_SPECIFIED);
$this->assertNormalized($f, '<a llama="awesome">link</a>', 'HTML filter keeps explicitly allowed attributes, and removes attributes that are not explicitly allowed.');
// Restrict the whitelisted "llama" attribute on <a> to only allow the value
// Restrict the allowed "llama" attribute on <a> to only allow the value
// "majestical", or "epic".
$filter->setConfiguration([
'settings' => [
@@ -2,8 +2,11 @@
namespace Drupal\Tests\filter\Kernel\Migrate\d7;
use Drupal\Core\Database\Database;
use Drupal\filter\Entity\FilterFormat;
use Drupal\filter\FilterFormatInterface;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\migrate\Kernel\MigrateDumpAlterInterface;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
@@ -11,7 +14,7 @@ use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
*
* @group filter
*/
class MigrateFilterFormatTest extends MigrateDrupal7TestBase {
class MigrateFilterFormatTest extends MigrateDrupal7TestBase implements MigrateDumpAlterInterface {
/**
* {@inheritdoc}
@@ -27,6 +30,30 @@ class MigrateFilterFormatTest extends MigrateDrupal7TestBase {
$this->executeMigration('d7_filter_format');
}
/**
* {@inheritdoc}
*/
public static function migrateDumpAlter(KernelTestBase $test) {
$db = Database::getConnection('default', 'migrate');
$fields = [
'format' => 'image_resize_filter',
'name' => 'Image resize',
'cache' => '1',
'status' => '1',
'weight' => '0',
];
$db->insert('filter_format')->fields($fields)->execute();
$fields = [
'format' => 'image_resize_filter',
'module' => 'filter',
'name' => 'image_resize_filter',
'weight' => '0',
'status' => '1',
'settings' => serialize([]),
];
$db->insert('filter')->fields($fields)->execute();
}
/**
* Asserts various aspects of a filter format entity.
*
@@ -80,6 +107,22 @@ class MigrateFilterFormatTest extends MigrateDrupal7TestBase {
// The disabled php_code format gets migrated, but the php_code filter is
// changed to filter_null.
$this->assertEntity('php_code', 'PHP code', ['filter_null' => 0], 11, FALSE);
// Test a non-existent format.
$this->assertEntity('image_resize_filter', 'Image resize', [], 0, TRUE);
// For each filter that does not exist on the destination, there should be
// a log message.
$migration = $this->getMigration('d7_filter_format');
$errors = array_map(function ($message) {
return $message->message;
}, iterator_to_array($migration->getIdMap()->getMessages()));
$this->assertCount(2, $errors);
sort($errors);
$message = 'Filter image_resize_filter could not be mapped to an existing filter plugin; omitted since it is a transformation-only filter. Install and configure a successor after the migration.';
$this->assertEquals($errors[0], $message);
$message = ('Filter php_code could not be mapped to an existing filter plugin; defaulting to filter_null and dropping all settings. Either redo the migration with the module installed that provides an equivalent filter, or modify the text format after the migration to remove this filter if it is no longer necessary.');
$this->assertEquals($errors[1], $message);
}
}
@@ -58,7 +58,7 @@ class DenormalizeTest extends NormalizerTestBase {
$this->fail('Exception should be thrown when type is invalid.');
}
catch (UnexpectedValueException $e) {
$this->pass('Exception thrown when type is invalid.');
// Expected exception; just continue testing.
}
// No type.
@@ -70,7 +70,7 @@ class DenormalizeTest extends NormalizerTestBase {
$this->fail('Exception should be thrown when no type is provided.');
}
catch (UnexpectedValueException $e) {
$this->pass('Exception thrown when no type is provided.');
// Expected exception; just continue testing.
}
}
@@ -35,15 +35,10 @@ class MigrateImageCacheTest extends MigrateDrupal6TestBase {
->condition('type', 'module')
->execute();
try {
$this->getMigration('d6_imagecache_presets')
->getSourcePlugin()
->checkRequirements();
$this->fail('Did not catch expected RequirementsException.');
}
catch (RequirementsException $e) {
$this->pass('Caught expected RequirementsException: ' . $e->getMessage());
}
$this->expectException(RequirementsException::class);
$this->getMigration('d6_imagecache_presets')
->getSourcePlugin()
->checkRequirements();
}
/**
@@ -162,7 +157,7 @@ class MigrateImageCacheTest extends MigrateDrupal6TestBase {
if ($effect_config['id'] == $id && $effect_config['data'] == $config) {
// We found this effect so succeed and return.
return $this->pass('Effect ' . $id . ' imported correctly');
return TRUE;
}
}
// The loop did not find the effect so we it was not imported correctly.
+2 -2
View File
@@ -63,7 +63,7 @@ use Drupal\Core\Access\AccessResult;
*
* @see https://github.com/json-api/json-api/pull/1268
* @see https://github.com/json-api/json-api/pull/1311
* @see https://www.drupal.org/project/jsonapi/issues/2955020
* @see https://www.drupal.org/project/drupal/issues/2955020
*
* By implementing revision support as a profile, the JSON:API module should be
* maximally compatible with other systems.
@@ -117,7 +117,7 @@ use Drupal\Core\Access\AccessResult;
* It is not yet possible to request a collection of revisions. This is still
* under development in issue [#3009588].
*
* @see https://www.drupal.org/project/jsonapi/issues/3009588.
* @see https://www.drupal.org/project/drupal/issues/3009588.
* @see https://tools.ietf.org/html/rfc5829
* @see https://www.drupal.org/docs/8/modules/jsonapi/revisions
*
@@ -31,7 +31,7 @@ use Symfony\Component\Routing\RouterInterface;
* @internal JSON:API maintains no PHP API. The API is the HTTP API. This class
* may change at any time and could break any dependencies on it.
*
* @see https://www.drupal.org/project/jsonapi/issues/3032787
* @see https://www.drupal.org/project/drupal/issues/3032787
* @see jsonapi.api.php
*/
class EntityAccessChecker {
@@ -238,7 +238,7 @@ class EntityAccessChecker {
*
* @todo: remove when a generic revision access API exists in Drupal core, and
* also remove the injected "node" and "media" services.
* @see https://www.drupal.org/project/jsonapi/issues/2992833#comment-12818386
* @see https://www.drupal.org/project/drupal/issues/2992833#comment-12818386
*/
protected function checkRevisionViewAccess(EntityInterface $entity, AccountInterface $account) {
assert($entity instanceof RevisionableInterface);
@@ -257,7 +257,7 @@ class EntityAccessChecker {
default:
$reason = 'Only node and media revisions are supported by JSON:API.';
$reason .= ' For context, see https://www.drupal.org/project/jsonapi/issues/2992833#comment-12818258.';
$reason .= ' For context, see https://www.drupal.org/project/drupal/issues/2992833#comment-12818258.';
$reason .= ' To contribute, see https://www.drupal.org/project/drupal/issues/2350939 and https://www.drupal.org/project/drupal/issues/2809177.';
$access = AccessResult::neutral($reason);
}
@@ -20,7 +20,7 @@ use Symfony\Component\Routing\Route;
* @internal JSON:API maintains no PHP API. The API is the HTTP API. This class
* may change at any time and could break any dependencies on it.
*
* @see https://www.drupal.org/project/jsonapi/issues/3032787
* @see https://www.drupal.org/project/drupal/issues/3032787
* @see jsonapi.api.php
*/
class RelationshipFieldAccess implements AccessInterface {
@@ -32,7 +32,7 @@ use Drupal\jsonapi\Query\Filter;
* @see https://www.drupal.org/project/drupal/issues/2809177
* @see https://www.drupal.org/project/drupal/issues/777578
*
* @see https://www.drupal.org/project/jsonapi/issues/3032787
* @see https://www.drupal.org/project/drupal/issues/3032787
* @see jsonapi.api.php
*/
class TemporaryQueryGuard {
@@ -67,7 +67,7 @@ use Drupal\Core\Http\Exception\CacheableBadRequestHttpException;
* @internal JSON:API maintains no PHP API. The API is the HTTP API. This class
* may change at any time and could break any dependencies on it.
*
* @see https://www.drupal.org/project/jsonapi/issues/3032787
* @see https://www.drupal.org/project/drupal/issues/3032787
* @see jsonapi.api.php
*/
class FieldResolver {
@@ -256,18 +256,21 @@ class FieldResolver {
* The JSON:API resource type from which to resolve the field name.
* @param string $external_field_name
* The public field name to map to a Drupal field name.
* @param string $operator
* (optional) The operator of the condition for which the path should be
* resolved.
*
* @return string
* The mapped field name.
*
* @throws \Drupal\Core\Http\Exception\CacheableBadRequestHttpException
*/
public function resolveInternalEntityQueryPath($resource_type, $external_field_name) {
public function resolveInternalEntityQueryPath($resource_type, $external_field_name, $operator = NULL) {
$function_args = func_get_args();
// @todo Remove this conditional block in drupal:9.0.0 and add a type hint
// to the first argument of this method.
// @see https://www.drupal.org/project/drupal/issues/3078045
if (count($function_args) === 3) {
if (count($function_args) === 3 && is_string($resource_type)) {
@trigger_error('Passing the entity type ID and bundle to ' . __METHOD__ . ' is deprecated in drupal:8.8.0 and will throw a fatal error in drupal:9.0.0. Pass a JSON:API resource type instead. See https://www.drupal.org/node/3078036', E_USER_DEPRECATED);
list($entity_type_id, $bundle, $external_field_name) = $function_args;
$resource_type = $this->resourceTypeRepository->get($entity_type_id, $bundle);
@@ -368,7 +371,10 @@ class FieldResolver {
// If there are no remaining path parts, the process is finished unless
// the field has multiple properties, in which case one must be specified.
if (empty($parts)) {
if ($property_specifier_needed) {
// If the operator is asserting the presence or absence of a
// relationship entirely, it does not make sense to require a property
// specifier.
if ($property_specifier_needed && (!$at_least_one_entity_reference_field || !in_array($operator, ['IS NULL', 'IS NOT NULL'], TRUE))) {
$possible_specifiers = array_map(function ($specifier) use ($at_least_one_entity_reference_field) {
return $at_least_one_entity_reference_field && $specifier !== 'id' ? "meta.$specifier" : $specifier;
}, $candidate_property_names);
@@ -538,7 +544,7 @@ class FieldResolver {
*/
protected function isMemberFilterable($external_name, array $resource_types) {
return array_reduce($resource_types, function ($carry, ResourceType $resource_type) use ($external_name) {
// @todo: remove the next line and uncomment the following one in https://www.drupal.org/project/jsonapi/issues/3017047.
// @todo: remove the next line and uncomment the following one in https://www.drupal.org/project/drupal/issues/3017047.
return $carry ?: $external_name === 'id' || $resource_type->isFieldEnabled($resource_type->getInternalName($external_name));
/*return $carry ?: in_array($external_name, ['id', 'type']) || $resource_type->isFieldEnabled($resource_type->getInternalName($external_name));*/
}, FALSE);
@@ -640,7 +646,7 @@ class FieldResolver {
$prior_parts = array_slice($unresolved_path_parts, 0, count($unresolved_path_parts) - count($remaining_parts));
return implode('.', array_merge($prior_parts, [$reference_name], $remaining_parts));
}, $unique_reference_names);
// @todo Add test coverage for this in https://www.drupal.org/project/jsonapi/issues/2971281
// @todo Add test coverage for this in https://www.drupal.org/project/drupal/issues/2971281
$message = sprintf('Ambiguous path. Try one of the following: %s, in place of the given path: %s', implode(', ', $choices), implode('.', $unresolved_path_parts));
$cacheability = (new CacheableMetadata())->addCacheContexts(['url.query_args:filter', 'url.query_args:sort']);
throw new CacheableBadRequestHttpException($cacheability, $message);
@@ -65,7 +65,7 @@ use Symfony\Component\Serializer\SerializerInterface;
* @internal JSON:API maintains no PHP API. The API is the HTTP API. This class
* may change at any time and could break any dependencies on it.
*
* @see https://www.drupal.org/project/jsonapi/issues/3032787
* @see https://www.drupal.org/project/drupal/issues/3032787
* @see jsonapi.api.php
*/
class EntityResource {
@@ -307,7 +307,7 @@ class EntityResource {
*/
public function patchIndividual(ResourceType $resource_type, EntityInterface $entity, Request $request) {
if ($entity instanceof RevisionableInterface && !($entity->isLatestRevision() && $entity->isDefaultRevision())) {
throw new BadRequestHttpException('Updating a resource object that has a working copy is not yet supported. See https://www.drupal.org/project/jsonapi/issues/2795279.');
throw new BadRequestHttpException('Updating a resource object that has a working copy is not yet supported. See https://www.drupal.org/project/drupal/issues/2795279.');
}
$parsed_entity = $this->deserialize($resource_type, $request, JsonApiDocumentTopLevel::class);
@@ -408,7 +408,7 @@ class EntityResource {
catch (\LogicException $e) {
// Ensure good DX when an entity query involves a config entity type.
// For example: getting users with a particular role, which is a config
// entity type: https://www.drupal.org/project/jsonapi/issues/2959445.
// entity type: https://www.drupal.org/project/drupal/issues/2959445.
// @todo Remove the message parsing in https://www.drupal.org/project/drupal/issues/3028967.
if (strpos($e->getMessage(), 'Getting the base fields is not supported for entity type') === 0) {
preg_match('/entity type (.*)\./', $e->getMessage(), $matches);
@@ -23,7 +23,7 @@ use Symfony\Component\Routing\Exception\RouteNotFoundException;
* @internal JSON:API maintains no PHP API. The API is the HTTP API. This class
* may change at any time and could break any dependencies on it.
*
* @see https://www.drupal.org/project/jsonapi/issues/3032787
* @see https://www.drupal.org/project/drupal/issues/3032787
* @see jsonapi.api.php
*/
class EntryPoint extends ControllerBase {
@@ -34,7 +34,7 @@ use Symfony\Component\Validator\ConstraintViolationInterface;
* @internal JSON:API maintains no PHP API. The API is the HTTP API. This class
* may change at any time and could break any dependencies on it.
*
* @see https://www.drupal.org/project/jsonapi/issues/3032787
* @see https://www.drupal.org/project/drupal/issues/3032787
* @see jsonapi.api.php
*/
class FileUpload {
@@ -179,7 +179,7 @@ class FileUpload {
throw new UnprocessableEntityHttpException($message);
}
// @todo Remove line below in favor of commented line in https://www.drupal.org/project/jsonapi/issues/2878463.
// @todo Remove line below in favor of commented line in https://www.drupal.org/project/drupal/issues/2878463.
$self_link = new Link(new CacheableMetadata(), Url::fromRoute('jsonapi.file--file.individual', ['entity' => $file->uuid()]), 'self');
/* $self_link = new Link(new CacheableMetadata(), $this->entity->toUrl('jsonapi'), ['self']); */
$links = new LinkCollection(['self' => $self_link]);
@@ -17,7 +17,7 @@ use Symfony\Component\DependencyInjection\Reference;
* @internal JSON:API maintains no PHP API. The API is the HTTP API. This class
* may change at any time and could break any dependencies on it.
*
* @see https://www.drupal.org/project/jsonapi/issues/3032787
* @see https://www.drupal.org/project/drupal/issues/3032787
* @see jsonapi.api.php
*/
class RegisterSerializationClassesCompilerPass extends DrupalRegisterSerializationClassesCompilerPass {
@@ -10,7 +10,7 @@ use Drupal\serialization\Encoder\JsonEncoder as SerializationJsonEncoder;
* @internal JSON:API maintains no PHP API. The API is the HTTP API. This class
* may change at any time and could break any dependencies on it.
*
* @see https://www.drupal.org/project/jsonapi/issues/3032787
* @see https://www.drupal.org/project/drupal/issues/3032787
* @see jsonapi.api.php
*/
class JsonEncoder extends SerializationJsonEncoder {

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