Tessier 3 роки тому
батько
коміт
d8b9162932
100 змінених файлів з 2045 додано та 664 видалено
  1. 211 168
      composer.lock
  2. 7 4
      web/core/assets/scaffold/files/default.settings.php
  3. 2 2
      web/core/core.libraries.yml
  4. 0 2
      web/core/drupalci.yml
  5. 2 1
      web/core/includes/entity.inc
  6. 4 4
      web/core/includes/pager.inc
  7. 3 3
      web/core/lib/Drupal.php
  8. 4 5
      web/core/lib/Drupal/Component/Datetime/DateTimePlus.php
  9. 18 0
      web/core/lib/Drupal/Component/Transliteration/PhpTransliteration.php
  10. 2 1
      web/core/lib/Drupal/Component/Utility/Bytes.php
  11. 0 2
      web/core/lib/Drupal/Component/Utility/Image.php
  12. 3 3
      web/core/lib/Drupal/Component/Utility/Unicode.php
  13. 1 1
      web/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php
  14. 1 262
      web/core/lib/Drupal/Core/Block/BlockBase.php
  15. 287 0
      web/core/lib/Drupal/Core/Block/BlockPluginTrait.php
  16. 8 2
      web/core/lib/Drupal/Core/Block/Plugin/Block/Broken.php
  17. 1 1
      web/core/lib/Drupal/Core/Config/ConfigEvents.php
  18. 5 0
      web/core/lib/Drupal/Core/Config/Entity/Query/Condition.php
  19. 8 1
      web/core/lib/Drupal/Core/Config/Entity/Query/Query.php
  20. 1 1
      web/core/lib/Drupal/Core/Config/Importer/MissingContentEvent.php
  21. 2 2
      web/core/lib/Drupal/Core/Database/Connection.php
  22. 2 2
      web/core/lib/Drupal/Core/Database/Driver/mysql/Schema.php
  23. 6 3
      web/core/lib/Drupal/Core/Database/Query/Select.php
  24. 1 1
      web/core/lib/Drupal/Core/Database/Schema.php
  25. 1 1
      web/core/lib/Drupal/Core/DependencyInjection/DeprecatedServicePropertyTrait.php
  26. 1 1
      web/core/lib/Drupal/Core/Entity/ContentEntityFormInterface.php
  27. 48 2
      web/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php
  28. 9 6
      web/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/DefaultSelection.php
  29. 13 2
      web/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
  30. 20 2
      web/core/lib/Drupal/Core/Entity/entity.api.php
  31. 1 0
      web/core/lib/Drupal/Core/EventSubscriber/ExceptionLoggingSubscriber.php
  32. 1 1
      web/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/BooleanCheckboxWidget.php
  33. 12 5
      web/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/EntityReferenceAutocompleteWidget.php
  34. 2 1
      web/core/lib/Drupal/Core/Form/FormBuilder.php
  35. 2 2
      web/core/lib/Drupal/Core/Link.php
  36. 2 2
      web/core/lib/Drupal/Core/Logger/RfcLogLevel.php
  37. 4 4
      web/core/lib/Drupal/Core/Render/Element/Email.php
  38. 1 1
      web/core/lib/Drupal/Core/Render/Element/StatusReport.php
  39. 6 3
      web/core/lib/Drupal/Core/Render/theme.api.php
  40. 2 2
      web/core/lib/Drupal/Core/Security/PharExtensionInterceptor.php
  41. 1 1
      web/core/lib/Drupal/Core/Template/AttributeHelper.php
  42. 2 2
      web/core/lib/Drupal/Core/Test/PhpUnitTestRunner.php
  43. 9 1
      web/core/lib/Drupal/Core/TypedData/TypedDataManager.php
  44. 40 6
      web/core/lib/Drupal/Core/Update/UpdateCompilerPass.php
  45. 1 1
      web/core/lib/Drupal/Core/Url.php
  46. 8 8
      web/core/lib/Drupal/Core/Utility/ProjectInfo.php
  47. 1 0
      web/core/misc/ajax.es6.js
  48. 1 0
      web/core/misc/ajax.js
  49. 1 0
      web/core/misc/autocomplete.es6.js
  50. 2 1
      web/core/misc/autocomplete.js
  51. 2 5
      web/core/modules/aggregator/tests/src/Functional/AggregatorTestBase.php
  52. 4 4
      web/core/modules/aggregator/tests/src/Functional/FeedParserTest.php
  53. 3 3
      web/core/modules/aggregator/tests/src/Functional/UpdateFeedItemTest.php
  54. 2 2
      web/core/modules/ban/tests/src/Functional/IpAddressBlockingTest.php
  55. 1 1
      web/core/modules/big_pipe/src/Render/Placeholder/BigPipeStrategy.php
  56. 19 18
      web/core/modules/big_pipe/tests/src/Functional/BigPipeTest.php
  57. 0 3
      web/core/modules/block/tests/src/Kernel/BlockViewBuilderTest.php
  58. 6 6
      web/core/modules/block/tests/src/Unit/Plugin/migrate/process/BlockVisibilityTest.php
  59. 1 1
      web/core/modules/block_content/src/Plugin/migrate/source/d7/BlockCustomTranslation.php
  60. 1 1
      web/core/modules/block_content/tests/src/Functional/BlockContentCreationTest.php
  61. 1 3
      web/core/modules/block_content/tests/src/Functional/BlockContentTranslationUITest.php
  62. 4 9
      web/core/modules/block_content/tests/src/Kernel/Migrate/MigrateBlockContentStubTest.php
  63. 2 3
      web/core/modules/book/src/Form/BookSettingsForm.php
  64. 1 1
      web/core/modules/book/tests/src/Functional/BookTestTrait.php
  65. 5 0
      web/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.es6.js
  66. 3 0
      web/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js
  67. 1 1
      web/core/modules/ckeditor/src/CKEditorPluginManager.php
  68. 2 1
      web/core/modules/color/tests/src/Functional/ColorTest.php
  69. 1 2
      web/core/modules/comment/comment.module
  70. 3 3
      web/core/modules/comment/src/CommentLazyBuilders.php
  71. 10 6
      web/core/modules/comment/src/CommentViewBuilder.php
  72. 1 1
      web/core/modules/comment/src/CommentViewsData.php
  73. 4 3
      web/core/modules/comment/src/Entity/Comment.php
  74. 1 1
      web/core/modules/comment/src/Plugin/views/field/NodeNewComments.php
  75. 8 0
      web/core/modules/comment/tests/modules/comment_base_field_test/comment_base_field_test.info.yml
  76. 6 0
      web/core/modules/comment/tests/modules/comment_base_field_test/config/install/comment.type.test_comment_type.yml
  77. 39 0
      web/core/modules/comment/tests/modules/comment_base_field_test/src/Entity/CommentTestBaseField.php
  78. 244 0
      web/core/modules/comment/tests/modules/comment_test_views/test_views/views.view.test_comment_count.yml
  79. 2 1
      web/core/modules/comment/tests/src/Functional/CommentAnonymousTest.php
  80. 1 1
      web/core/modules/comment/tests/src/Functional/CommentInterfaceTest.php
  81. 2 1
      web/core/modules/comment/tests/src/Functional/CommentNonNodeTest.php
  82. 0 1
      web/core/modules/comment/tests/src/Functional/CommentPagerTest.php
  83. 3 2
      web/core/modules/comment/tests/src/Functional/CommentTitleTest.php
  84. 1 1
      web/core/modules/comment/tests/src/Functional/CommentTypeTest.php
  85. 27 1
      web/core/modules/comment/tests/src/Functional/Views/NodeCommentsTest.php
  86. 64 0
      web/core/modules/comment/tests/src/Kernel/CommentBaseFieldTest.php
  87. 134 0
      web/core/modules/comment/tests/src/Kernel/CommentOrphanTest.php
  88. 17 22
      web/core/modules/comment/tests/src/Kernel/CommentStringIdEntitiesTest.php
  89. 1 0
      web/core/modules/comment/tests/src/Kernel/Migrate/d7/MigrateCommentFieldInstanceTest.php
  90. 1 0
      web/core/modules/comment/tests/src/Kernel/Migrate/d7/MigrateCommentFieldTest.php
  91. 1 1
      web/core/modules/config/src/Form/ConfigSingleImportForm.php
  92. 1 1
      web/core/modules/config/tests/config_schema_test/config_schema_test.module
  93. 5 15
      web/core/modules/config/tests/src/Functional/ConfigEntityTest.php
  94. 4 0
      web/core/modules/config/tests/src/Functional/ConfigSingleImportExportTest.php
  95. 3 9
      web/core/modules/config/tests/src/Functional/SchemaConfigListenerWebTest.php
  96. 4 0
      web/core/modules/content_moderation/src/Plugin/views/ModerationStateJoinViewsHandlerTrait.php
  97. 12 3
      web/core/modules/content_moderation/src/Plugin/views/filter/ModerationStateFilter.php
  98. 348 0
      web/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_filter_via_relationship.yml
  99. 268 0
      web/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_state_filter_base_table_filter_group_or.yml
  100. 1 1
      web/core/modules/content_moderation/tests/src/Kernel/ContentModerationAccessTest.php

Різницю між файлами не показано, бо вона завелика
+ 211 - 168
composer.lock


+ 7 - 4
web/core/assets/scaffold/files/default.settings.php

@@ -781,10 +781,13 @@ $settings['migrate_node_migrate_type_classic'] = FALSE;
 /**
  * Load local development override configuration, if available.
  *
- * Use settings.local.php to override variables on secondary (staging,
- * development, etc) installations of this site. Typically used to disable
- * caching, JavaScript/CSS compression, re-routing of outgoing emails, and
- * other things that should not happen on development and testing sites.
+ * Create a settings.local.php file to override variables on secondary (staging,
+ * development, etc.) installations of this site.
+ *
+ * Typical uses of settings.local.php include:
+ * - Disabling caching.
+ * - Disabling JavaScript/CSS compression.
+ * - Rerouting outgoing emails.
  *
  * Keep this code block at the end of this file to take full effect.
  */

+ 2 - 2
web/core/core.libraries.yml

@@ -25,10 +25,10 @@ classList:
 
 ckeditor:
   remote: https://github.com/ckeditor/ckeditor-dev
-  version: "4.14.0"
+  version: "4.14.1"
   license:
     name: GNU-GPL-2.0-or-later
-    url: https://github.com/ckeditor/ckeditor4/blob/4.14.0/LICENSE.md
+    url: https://github.com/ckeditor/ckeditor4/blob/4.14.1/LICENSE.md
     gpl-compatible: true
   js:
     assets/vendor/ckeditor/ckeditor.js: { preprocess: false, minified: true }

+ 0 - 2
web/core/drupalci.yml

@@ -5,8 +5,6 @@ build:
   assessment:
     validate_codebase:
       phplint:
-      csslint:
-        halt-on-fail: false
       eslint:
         # A test must pass eslinting standards check in order to continue processing.
         halt-on-fail: false

+ 2 - 1
web/core/includes/entity.inc

@@ -233,7 +233,8 @@ function entity_load_unchanged($entity_type, $id) {
  *   An array of entity IDs of the entities to delete.
  *
  * @deprecated in drupal:8.0.0 and is removed from drupal:9.0.0. Use
- *   the entity storage's delete() method to delete multiple entities:
+ *   the entity storage's \Drupal\Core\Entity\EntityStorageInterface::delete()
+ *   method to delete multiple entities:
  *   @code
  *     $storage_handler = \Drupal::entityTypeManager()->getStorage($entity_type);
  *     $entities = $storage_handler->loadMultiple($ids);

+ 4 - 4
web/core/includes/pager.inc

@@ -26,13 +26,13 @@ use Drupal\Component\Utility\Html;
  *   displays the third page of search results at that URL.
  *
  * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use
- *   \Drupal\Core\Pager\RequestPagerInterface->findPage() instead.
+ *   \Drupal\Core\Pager\PagerParametersInterface->findPage() instead.
  *
  * @see https://www.drupal.org/node/2779457
  * @see \Drupal\Core\Pager\PagerParametersInterface::findPage()
  */
 function pager_find_page($element = 0) {
-  @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Pager\RequestPagerInterface->findPage() instead. See https://www.drupal.org/node/2779457', E_USER_DEPRECATED);
+  @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Pager\PagerParametersInterface->findPage() instead. See https://www.drupal.org/node/2779457', E_USER_DEPRECATED);
   /* @var $pager_parameters \Drupal\Core\Pager\PagerParametersInterface */
   $pager_parameters = \Drupal::service('pager.parameters');
   return $pager_parameters->findPage($element);
@@ -150,13 +150,13 @@ function pager_default_initialize($total, $limit, $element = 0) {
  *   page request except for those pertaining to paging.
  *
  * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use
- *   \Drupal\Core\Pager\RequestPagerInterface->getQueryParameters() instead.
+ *   \Drupal\Core\Pager\PagerParametersInterface->getQueryParameters() instead.
  *
  * @see https://www.drupal.org/node/2779457
  * @see \Drupal\Core\Pager\PagerParametersInterface::getQueryParameters()
  */
 function pager_get_query_parameters() {
-  @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Pager\RequestPagerInterface->getQueryParameters() instead. See https://www.drupal.org/node/2779457', E_USER_DEPRECATED);
+  @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Pager\PagerParametersInterface->getQueryParameters() instead. See https://www.drupal.org/node/2779457', E_USER_DEPRECATED);
   /* @var $pager_params \Drupal\Core\Pager\PagerParametersInterface */
   $pager_params = \Drupal::service('pager.parameters');
   return $pager_params->getQueryParameters();

+ 3 - 3
web/core/lib/Drupal.php

@@ -82,7 +82,7 @@ class Drupal {
   /**
    * The current system version.
    */
-  const VERSION = '8.9.2';
+  const VERSION = '8.9.7';
 
   /**
    * Core API compatibility.
@@ -606,8 +606,8 @@ class Drupal {
    * This method is a convenience wrapper for the link generator service's
    * generate() method.
    *
-   * @param string $text
-   *   The link text for the anchor tag.
+   * @param string|array|\Drupal\Component\Render\MarkupInterface $text
+   *   The link text for the anchor tag as a translated string or render array.
    * @param \Drupal\Core\Url $url
    *   The URL object used for the link.
    *

+ 4 - 5
web/core/lib/Drupal/Component/Datetime/DateTimePlus.php

@@ -624,11 +624,10 @@ class DateTimePlus {
     $valid_date = FALSE;
     $valid_time = TRUE;
     // Check for a valid date using checkdate(). Only values that
-    // meet that test are valid.
-    if (array_key_exists('year', $array) && array_key_exists('month', $array) && array_key_exists('day', $array)) {
-      if (@checkdate($array['month'], $array['day'], $array['year'])) {
-        $valid_date = TRUE;
-      }
+    // meet that test are valid. An empty value, either a string or a 0, is not
+    // a valid value.
+    if (!empty($array['year']) && !empty($array['month']) && !empty($array['day'])) {
+      $valid_date = checkdate($array['month'], $array['day'], $array['year']);
     }
     // Testing for valid time is reversed. Missing time is OK,
     // but incorrect values are not.

+ 18 - 0
web/core/lib/Drupal/Component/Transliteration/PhpTransliteration.php

@@ -58,6 +58,21 @@ class PhpTransliteration implements TransliterationInterface {
    */
   protected $genericMap = [];
 
+  /**
+   * Special characters for ::removeDiacritics().
+   *
+   * Characters which have accented variants but their base character
+   * transliterates to more than one ASCII character require special
+   * treatment: we want to remove their accent and use the un-
+   * transliterated base character.
+   */
+  protected $fixTransliterateForRemoveDiacritics = [
+    'AE' => 'Æ',
+    'ae' => 'æ',
+    'ZH' => 'Ʒ',
+    'zh' => 'ʒ',
+  ];
+
   /**
    * Constructs a transliteration object.
    *
@@ -93,6 +108,9 @@ class PhpTransliteration implements TransliterationInterface {
         if (strlen($to_add) === 1) {
           $replacement = $to_add;
         }
+        elseif (isset($this->fixTransliterateForRemoveDiacritics[$to_add])) {
+          $replacement = $this->fixTransliterateForRemoveDiacritics[$to_add];
+        }
       }
 
       $result .= $replacement;

+ 2 - 1
web/core/lib/Drupal/Component/Utility/Bytes.php

@@ -35,7 +35,8 @@ class Bytes {
       return round($size * pow(self::KILOBYTE, stripos('bkmgtpezy', $unit[0])));
     }
     else {
-      return round($size);
+      // Ensure size is a proper number type.
+      return round((float) $size);
     }
   }
 

+ 0 - 2
web/core/lib/Drupal/Component/Utility/Image.php

@@ -30,8 +30,6 @@ class Image {
    *
    * @return bool
    *   TRUE if $dimensions was modified, FALSE otherwise.
-   *
-   * @see image_scale()
    */
   public static function scaleDimensions(array &$dimensions, $width = NULL, $height = NULL, $upscale = FALSE) {
     $aspect = $dimensions['height'] / $dimensions['width'];

+ 3 - 3
web/core/lib/Drupal/Component/Utility/Unicode.php

@@ -537,16 +537,16 @@ EOD;
    */
   public static function mimeHeaderDecode($header) {
     $callback = function ($matches) {
-      $data = ($matches[2] == 'B') ? base64_decode($matches[3]) : str_replace('_', ' ', quoted_printable_decode($matches[3]));
+      $data = (strtolower($matches[2]) == 'b') ? base64_decode($matches[3]) : str_replace('_', ' ', quoted_printable_decode($matches[3]));
       if (strtolower($matches[1]) != 'utf-8') {
         $data = static::convertToUtf8($data, $matches[1]);
       }
       return $data;
     };
     // First step: encoded chunks followed by other encoded chunks (need to collapse whitespace)
-    $header = preg_replace_callback('/=\?([^?]+)\?(Q|B)\?([^?]+|\?(?!=))\?=\s+(?==\?)/', $callback, $header);
+    $header = preg_replace_callback('/=\?([^?]+)\?([Qq]|[Bb])\?([^?]+|\?(?!=))\?=\s+(?==\?)/', $callback, $header);
     // Second step: remaining chunks (do not collapse whitespace)
-    return preg_replace_callback('/=\?([^?]+)\?(Q|B)\?([^?]+|\?(?!=))\?=/', $callback, $header);
+    return preg_replace_callback('/=\?([^?]+)\?([Qq]|[Bb])\?([^?]+|\?(?!=))\?=/', $callback, $header);
   }
 
   /**

+ 1 - 1
web/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php

@@ -345,7 +345,7 @@ class LibraryDiscoveryParser {
     $library_file = $path . '/' . $extension . '.libraries.yml';
     if (file_exists($this->root . '/' . $library_file)) {
       try {
-        $libraries = Yaml::decode(file_get_contents($this->root . '/' . $library_file));
+        $libraries = Yaml::decode(file_get_contents($this->root . '/' . $library_file)) ?? [];
       }
       catch (InvalidDataTypeException $e) {
         // Rethrow a more helpful exception to provide context.

+ 1 - 262
web/core/lib/Drupal/Core/Block/BlockBase.php

@@ -2,18 +2,10 @@
 
 namespace Drupal\Core\Block;
 
-use Drupal\Core\Access\AccessResult;
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Messenger\MessengerTrait;
 use Drupal\Core\Plugin\ContextAwarePluginAssignmentTrait;
 use Drupal\Core\Plugin\ContextAwarePluginBase;
-use Drupal\Component\Utility\NestedArray;
-use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Plugin\PluginWithFormsInterface;
-use Drupal\Core\Plugin\PluginWithFormsTrait;
 use Drupal\Core\Render\PreviewFallbackInterface;
-use Drupal\Core\Session\AccountInterface;
-use Drupal\Component\Transliteration\TransliterationInterface;
 
 /**
  * Defines a base block implementation that most blocks plugins will extend.
@@ -26,260 +18,7 @@ use Drupal\Component\Transliteration\TransliterationInterface;
  */
 abstract class BlockBase extends ContextAwarePluginBase implements BlockPluginInterface, PluginWithFormsInterface, PreviewFallbackInterface {
 
+  use BlockPluginTrait;
   use ContextAwarePluginAssignmentTrait;
-  use MessengerTrait;
-  use PluginWithFormsTrait;
-
-  /**
-   * The transliteration service.
-   *
-   * @var \Drupal\Component\Transliteration\TransliterationInterface
-   */
-  protected $transliteration;
-
-  /**
-   * {@inheritdoc}
-   */
-  public function label() {
-    if (!empty($this->configuration['label'])) {
-      return $this->configuration['label'];
-    }
-
-    $definition = $this->getPluginDefinition();
-    // Cast the admin label to a string since it is an object.
-    // @see \Drupal\Core\StringTranslation\TranslatableMarkup
-    return (string) $definition['admin_label'];
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
-    parent::__construct($configuration, $plugin_id, $plugin_definition);
-    $this->setConfiguration($configuration);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getConfiguration() {
-    return $this->configuration;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setConfiguration(array $configuration) {
-    $this->configuration = NestedArray::mergeDeep(
-      $this->baseConfigurationDefaults(),
-      $this->defaultConfiguration(),
-      $configuration
-    );
-  }
-
-  /**
-   * Returns generic default configuration for block plugins.
-   *
-   * @return array
-   *   An associative array with the default configuration.
-   */
-  protected function baseConfigurationDefaults() {
-    return [
-      'id' => $this->getPluginId(),
-      'label' => '',
-      'provider' => $this->pluginDefinition['provider'],
-      'label_display' => static::BLOCK_LABEL_VISIBLE,
-    ];
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function defaultConfiguration() {
-    return [];
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setConfigurationValue($key, $value) {
-    $this->configuration[$key] = $value;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function calculateDependencies() {
-    return [];
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function access(AccountInterface $account, $return_as_object = FALSE) {
-    $access = $this->blockAccess($account);
-    return $return_as_object ? $access : $access->isAllowed();
-  }
-
-  /**
-   * Indicates whether the block should be shown.
-   *
-   * Blocks with specific access checking should override this method rather
-   * than access(), in order to avoid repeating the handling of the
-   * $return_as_object argument.
-   *
-   * @param \Drupal\Core\Session\AccountInterface $account
-   *   The user session for which to check access.
-   *
-   * @return \Drupal\Core\Access\AccessResult
-   *   The access result.
-   *
-   * @see self::access()
-   */
-  protected function blockAccess(AccountInterface $account) {
-    // By default, the block is visible.
-    return AccessResult::allowed();
-  }
-
-  /**
-   * {@inheritdoc}
-   *
-   * Creates a generic configuration form for all block types. Individual
-   * block plugins can add elements to this form by overriding
-   * BlockBase::blockForm(). Most block plugins should not override this
-   * method unless they need to alter the generic form elements.
-   *
-   * @see \Drupal\Core\Block\BlockBase::blockForm()
-   */
-  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
-    $definition = $this->getPluginDefinition();
-    $form['provider'] = [
-      '#type' => 'value',
-      '#value' => $definition['provider'],
-    ];
-
-    $form['admin_label'] = [
-      '#type' => 'item',
-      '#title' => $this->t('Block description'),
-      '#plain_text' => $definition['admin_label'],
-    ];
-    $form['label'] = [
-      '#type' => 'textfield',
-      '#title' => $this->t('Title'),
-      '#maxlength' => 255,
-      '#default_value' => $this->label(),
-      '#required' => TRUE,
-    ];
-    $form['label_display'] = [
-      '#type' => 'checkbox',
-      '#title' => $this->t('Display title'),
-      '#default_value' => ($this->configuration['label_display'] === static::BLOCK_LABEL_VISIBLE),
-      '#return_value' => static::BLOCK_LABEL_VISIBLE,
-    ];
-
-    // Add context mapping UI form elements.
-    $contexts = $form_state->getTemporaryValue('gathered_contexts') ?: [];
-    $form['context_mapping'] = $this->addContextAssignmentElement($this, $contexts);
-    // Add plugin-specific settings for this block type.
-    $form += $this->blockForm($form, $form_state);
-    return $form;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function blockForm($form, FormStateInterface $form_state) {
-    return [];
-  }
-
-  /**
-   * {@inheritdoc}
-   *
-   * Most block plugins should not override this method. To add validation
-   * for a specific block type, override BlockBase::blockValidate().
-   *
-   * @see \Drupal\Core\Block\BlockBase::blockValidate()
-   */
-  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
-    // Remove the admin_label form item element value so it will not persist.
-    $form_state->unsetValue('admin_label');
-
-    $this->blockValidate($form, $form_state);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function blockValidate($form, FormStateInterface $form_state) {}
-
-  /**
-   * {@inheritdoc}
-   *
-   * Most block plugins should not override this method. To add submission
-   * handling for a specific block type, override BlockBase::blockSubmit().
-   *
-   * @see \Drupal\Core\Block\BlockBase::blockSubmit()
-   */
-  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
-    // Process the block's submission handling if no errors occurred only.
-    if (!$form_state->getErrors()) {
-      $this->configuration['label'] = $form_state->getValue('label');
-      $this->configuration['label_display'] = $form_state->getValue('label_display');
-      $this->configuration['provider'] = $form_state->getValue('provider');
-      $this->blockSubmit($form, $form_state);
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function blockSubmit($form, FormStateInterface $form_state) {}
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getMachineNameSuggestion() {
-    $definition = $this->getPluginDefinition();
-    $admin_label = $definition['admin_label'];
-
-    // @todo This is basically the same as what is done in
-    //   \Drupal\system\MachineNameController::transliterate(), so it might make
-    //   sense to provide a common service for the two.
-    $transliterated = $this->transliteration()->transliterate($admin_label, LanguageInterface::LANGCODE_DEFAULT, '_');
-    $transliterated = mb_strtolower($transliterated);
-
-    $transliterated = preg_replace('@[^a-z0-9_.]+@', '', $transliterated);
-
-    return $transliterated;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getPreviewFallbackString() {
-    return $this->t('"@block" block', ['@block' => $this->label()]);
-  }
-
-  /**
-   * Wraps the transliteration service.
-   *
-   * @return \Drupal\Component\Transliteration\TransliterationInterface
-   */
-  protected function transliteration() {
-    if (!$this->transliteration) {
-      $this->transliteration = \Drupal::transliteration();
-    }
-    return $this->transliteration;
-  }
-
-  /**
-   * Sets the transliteration service.
-   *
-   * @param \Drupal\Component\Transliteration\TransliterationInterface $transliteration
-   *   The transliteration service.
-   */
-  public function setTransliteration(TransliterationInterface $transliteration) {
-    $this->transliteration = $transliteration;
-  }
 
 }

+ 287 - 0
web/core/lib/Drupal/Core/Block/BlockPluginTrait.php

@@ -0,0 +1,287 @@
+<?php
+
+namespace Drupal\Core\Block;
+
+use Drupal\Component\Transliteration\TransliterationInterface;
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Messenger\MessengerTrait;
+use Drupal\Core\Plugin\PluginWithFormsTrait;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Provides the base implementation of a block plugin.
+ *
+ * @internal
+ *   This trait is used internally by the block system. Block plugins should
+ *   extend \Drupal\Core\Block\BlockBase.
+ *
+ * @see \Drupal\Core\Block\BlockBase
+ * @see \Drupal\Core\Block\BlockPluginInterface
+ *
+ * @ingroup block_api
+ */
+trait BlockPluginTrait {
+
+  use StringTranslationTrait;
+  use MessengerTrait;
+  use PluginWithFormsTrait;
+
+  /**
+   * The transliteration service.
+   *
+   * @var \Drupal\Component\Transliteration\TransliterationInterface
+   */
+  protected $transliteration;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function label() {
+    if (!empty($this->configuration['label'])) {
+      return $this->configuration['label'];
+    }
+
+    $definition = $this->getPluginDefinition();
+    // Cast the admin label to a string since it is an object.
+    // @see \Drupal\Core\StringTranslation\TranslatableMarkup
+    return (string) $definition['admin_label'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->setConfiguration($configuration);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfiguration() {
+    return $this->configuration;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setConfiguration(array $configuration) {
+    $this->configuration = NestedArray::mergeDeep(
+      $this->baseConfigurationDefaults(),
+      $this->defaultConfiguration(),
+      $configuration
+    );
+  }
+
+  /**
+   * Returns generic default configuration for block plugins.
+   *
+   * @return array
+   *   An associative array with the default configuration.
+   */
+  protected function baseConfigurationDefaults() {
+    return [
+      'id' => $this->getPluginId(),
+      'label' => '',
+      'provider' => $this->pluginDefinition['provider'],
+      'label_display' => BlockPluginInterface::BLOCK_LABEL_VISIBLE,
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function defaultConfiguration() {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setConfigurationValue($key, $value) {
+    $this->configuration[$key] = $value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function calculateDependencies() {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access(AccountInterface $account, $return_as_object = FALSE) {
+    $access = $this->blockAccess($account);
+    return $return_as_object ? $access : $access->isAllowed();
+  }
+
+  /**
+   * Indicates whether the block should be shown.
+   *
+   * Blocks with specific access checking should override this method rather
+   * than access(), in order to avoid repeating the handling of the
+   * $return_as_object argument.
+   *
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The user session for which to check access.
+   *
+   * @return \Drupal\Core\Access\AccessResult
+   *   The access result.
+   *
+   * @see self::access()
+   */
+  protected function blockAccess(AccountInterface $account) {
+    // By default, the block is visible.
+    return AccessResult::allowed();
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * Creates a generic configuration form for all block types. Individual
+   * block plugins can add elements to this form by overriding
+   * BlockBase::blockForm(). Most block plugins should not override this
+   * method unless they need to alter the generic form elements.
+   *
+   * @see \Drupal\Core\Block\BlockBase::blockForm()
+   */
+  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+    $definition = $this->getPluginDefinition();
+    $form['provider'] = [
+      '#type' => 'value',
+      '#value' => $definition['provider'],
+    ];
+
+    $form['admin_label'] = [
+      '#type' => 'item',
+      '#title' => $this->t('Block description'),
+      '#plain_text' => $definition['admin_label'],
+    ];
+    $form['label'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Title'),
+      '#maxlength' => 255,
+      '#default_value' => $this->label(),
+      '#required' => TRUE,
+    ];
+    $form['label_display'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Display title'),
+      '#default_value' => ($this->configuration['label_display'] === BlockPluginInterface::BLOCK_LABEL_VISIBLE),
+      '#return_value' => BlockPluginInterface::BLOCK_LABEL_VISIBLE,
+    ];
+
+    // Add context mapping UI form elements.
+    $contexts = $form_state->getTemporaryValue('gathered_contexts') ?: [];
+    $form['context_mapping'] = $this->addContextAssignmentElement($this, $contexts);
+    // Add plugin-specific settings for this block type.
+    $form += $this->blockForm($form, $form_state);
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function blockForm($form, FormStateInterface $form_state) {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * Most block plugins should not override this method. To add validation
+   * for a specific block type, override BlockBase::blockValidate().
+   *
+   * @see \Drupal\Core\Block\BlockBase::blockValidate()
+   */
+  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
+    // Remove the admin_label form item element value so it will not persist.
+    $form_state->unsetValue('admin_label');
+
+    $this->blockValidate($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function blockValidate($form, FormStateInterface $form_state) {
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * Most block plugins should not override this method. To add submission
+   * handling for a specific block type, override BlockBase::blockSubmit().
+   *
+   * @see \Drupal\Core\Block\BlockBase::blockSubmit()
+   */
+  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
+    // Process the block's submission handling if no errors occurred only.
+    if (!$form_state->getErrors()) {
+      $this->configuration['label'] = $form_state->getValue('label');
+      $this->configuration['label_display'] = $form_state->getValue('label_display');
+      $this->configuration['provider'] = $form_state->getValue('provider');
+      $this->blockSubmit($form, $form_state);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function blockSubmit($form, FormStateInterface $form_state) {
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMachineNameSuggestion() {
+    $definition = $this->getPluginDefinition();
+    $admin_label = $definition['admin_label'];
+
+    // @todo This is basically the same as what is done in
+    //   \Drupal\system\MachineNameController::transliterate(), so it might make
+    //   sense to provide a common service for the two.
+    $transliterated = $this->transliteration()->transliterate($admin_label, LanguageInterface::LANGCODE_DEFAULT, '_');
+    $transliterated = mb_strtolower($transliterated);
+
+    $transliterated = preg_replace('@[^a-z0-9_.]+@', '', $transliterated);
+
+    return $transliterated;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPreviewFallbackString() {
+    return $this->t('"@block" block', ['@block' => $this->label()]);
+  }
+
+  /**
+   * Wraps the transliteration service.
+   *
+   * @return \Drupal\Component\Transliteration\TransliterationInterface
+   */
+  protected function transliteration() {
+    if (!$this->transliteration) {
+      $this->transliteration = \Drupal::transliteration();
+    }
+    return $this->transliteration;
+  }
+
+  /**
+   * Sets the transliteration service.
+   *
+   * @param \Drupal\Component\Transliteration\TransliterationInterface $transliteration
+   *   The transliteration service.
+   */
+  public function setTransliteration(TransliterationInterface $transliteration) {
+    $this->transliteration = $transliteration;
+  }
+
+}

+ 8 - 2
web/core/lib/Drupal/Core/Block/Plugin/Block/Broken.php

@@ -2,8 +2,11 @@
 
 namespace Drupal\Core\Block\Plugin\Block;
 
-use Drupal\Core\Block\BlockBase;
+use Drupal\Core\Block\BlockPluginInterface;
+use Drupal\Core\Block\BlockPluginTrait;
+use Drupal\Core\Cache\CacheableDependencyTrait;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\PluginBase;
 
 /**
  * Defines a fallback plugin for missing block plugins.
@@ -14,7 +17,10 @@ use Drupal\Core\Form\FormStateInterface;
  *   category = @Translation("Block"),
  * )
  */
-class Broken extends BlockBase {
+class Broken extends PluginBase implements BlockPluginInterface {
+
+  use BlockPluginTrait;
+  use CacheableDependencyTrait;
 
   /**
    * {@inheritdoc}

+ 1 - 1
web/core/lib/Drupal/Core/Config/ConfigEvents.php

@@ -121,7 +121,7 @@ final class ConfigEvents {
    * fire the event again to continue processing missing content dependencies.
    *
    * @see \Drupal\Core\Config\ConfigImporter::processMissingContent()
-   * @see \Drupal\Core\Config\MissingContentEvent
+   * @see \Drupal\Core\Config\Importer\MissingContentEvent
    */
   const IMPORT_MISSING_CONTENT = 'config.importer.missing_content';
 

+ 5 - 0
web/core/lib/Drupal/Core/Config/Entity/Query/Condition.php

@@ -130,6 +130,11 @@ class Condition extends ConditionBase {
             return TRUE;
           }
         }
+        // If the parent does not exist, it's safe to say the actual property
+        // we're checking for is also NULL.
+        elseif ($condition['operator'] === 'IS NULL') {
+          return TRUE;
+        }
       }
       // Only try to match a scalar if there are no remaining keys in
       // $needs_matching as this indicates that we are looking for a specific

+ 8 - 1
web/core/lib/Drupal/Core/Config/Entity/Query/Query.php

@@ -89,7 +89,14 @@ class Query extends QueryBase implements QueryInterface {
       $direction = $sort['direction'] == 'ASC' ? -1 : 1;
       $field = $sort['field'];
       uasort($result, function ($a, $b) use ($field, $direction) {
-        return ($a[$field] <= $b[$field]) ? $direction : -$direction;
+        $properties = explode('.', $field);
+        foreach ($properties as $property) {
+          if (isset($a[$property]) || isset($b[$property])) {
+            $a = isset($a[$property]) ? $a[$property] : NULL;
+            $b = isset($b[$property]) ? $b[$property] : NULL;
+          }
+        }
+        return ($a <= $b) ? $direction : -$direction;
       });
     }
 

+ 1 - 1
web/core/lib/Drupal/Core/Config/Importer/MissingContentEvent.php

@@ -7,7 +7,7 @@ use Symfony\Component\EventDispatcher\Event;
 /**
  * Wraps a configuration event for event listeners.
  *
- * @see \Drupal\Core\Config\Config\ConfigEvents::IMPORT_MISSING_CONTENT
+ * @see \Drupal\Core\Config\ConfigEvents::IMPORT_MISSING_CONTENT
  */
 class MissingContentEvent extends Event {
 

+ 2 - 2
web/core/lib/Drupal/Core/Database/Connection.php

@@ -1501,8 +1501,8 @@ abstract class Connection {
   /**
    * Prepares a statement for execution and returns a statement object
    *
-   * Emulated prepared statements does not communicate with the database server
-   * so this method does not check the statement.
+   * Emulated prepared statements do not communicate with the database server so
+   * this method does not check the statement.
    *
    * @param string $statement
    *   This must be a valid SQL statement for the target database server.

+ 2 - 2
web/core/lib/Drupal/Core/Database/Driver/mysql/Schema.php

@@ -686,11 +686,11 @@ class Schema extends DatabaseSchema {
       $condition->condition('column_name', $column);
       $condition->compile($this->connection, $this);
       // Don't use {} around information_schema.columns table.
-      return $this->connection->query("SELECT column_comment as column_comment FROM information_schema.columns WHERE " . (string) $condition, $condition->arguments())->fetchField();
+      return $this->connection->query("SELECT column_comment AS column_comment FROM information_schema.columns WHERE " . (string) $condition, $condition->arguments())->fetchField();
     }
     $condition->compile($this->connection, $this);
     // Don't use {} around information_schema.tables table.
-    $comment = $this->connection->query("SELECT table_comment as table_comment FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments())->fetchField();
+    $comment = $this->connection->query("SELECT table_comment AS table_comment FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments())->fetchField();
     // Work-around for MySQL 5.0 bug http://bugs.mysql.com/bug.php?id=11379
     return preg_replace('/; InnoDB free:.*$/', '', $comment);
   }

+ 6 - 3
web/core/lib/Drupal/Core/Database/Query/Select.php

@@ -814,13 +814,16 @@ class Select extends Query implements SelectInterface {
     $fields = [];
     foreach ($this->tables as $alias => $table) {
       if (!empty($table['all_fields'])) {
-        $fields[] = $this->connection->escapeTable($alias) . '.*';
+        $fields[] = $this->connection->escapeAlias($alias) . '.*';
       }
     }
     foreach ($this->fields as $field) {
+      // Note that $field['table'] holds the table alias.
+      // @see \Drupal\Core\Database\Query\Select::addField
+      $table = isset($field['table']) ? $this->connection->escapeAlias($field['table']) . '.' : '';
       // Always use the AS keyword for field aliases, as some
       // databases require it (e.g., PostgreSQL).
-      $fields[] = (isset($field['table']) ? $this->connection->escapeTable($field['table']) . '.' : '') . $this->connection->escapeField($field['field']) . ' AS ' . $this->connection->escapeAlias($field['alias']);
+      $fields[] = $table . $this->connection->escapeField($field['field']) . ' AS ' . $this->connection->escapeAlias($field['alias']);
     }
     foreach ($this->expressions as $expression) {
       $fields[] = $expression['expression'] . ' AS ' . $this->connection->escapeAlias($expression['alias']);
@@ -852,7 +855,7 @@ class Select extends Query implements SelectInterface {
 
       // Don't use the AS keyword for table aliases, as some
       // databases don't support it (e.g., Oracle).
-      $query .= $table_string . ' ' . $this->connection->escapeTable($table['alias']);
+      $query .= $table_string . ' ' . $this->connection->escapeAlias($table['alias']);
 
       if (!empty($table['condition'])) {
         $query .= ' ON ' . (string) $table['condition'];

+ 1 - 1
web/core/lib/Drupal/Core/Database/Schema.php

@@ -200,7 +200,7 @@ abstract class Schema implements PlaceholderInterface {
     // couldn't use \Drupal::database()->select() here because it would prefix
     // information_schema.tables and the query would fail.
     // Don't use {} around information_schema.tables table.
-    $results = $this->connection->query("SELECT table_name as table_name FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments());
+    $results = $this->connection->query("SELECT table_name AS table_name FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments());
     foreach ($results as $table) {
       // Take into account tables that have an individual prefix.
       if (isset($individually_prefixed_tables[$table->table_name])) {

+ 1 - 1
web/core/lib/Drupal/Core/DependencyInjection/DeprecatedServicePropertyTrait.php

@@ -8,7 +8,7 @@ namespace Drupal\Core\DependencyInjection;
 trait DeprecatedServicePropertyTrait {
 
   /**
-   * Alows to access deprecated/removed properties.
+   * Allows to access deprecated/removed properties.
    *
    * This method must be public.
    */

+ 1 - 1
web/core/lib/Drupal/Core/Entity/ContentEntityFormInterface.php

@@ -70,7 +70,7 @@ interface ContentEntityFormInterface extends EntityFormInterface {
    * For more information about entity validation, see
    * https://www.drupal.org/node/2015613.
    *
-   * @return \Drupal\Core\Entity\ContentEntityTypeInterface
+   * @return \Drupal\Core\Entity\ContentEntityInterface
    *   The built entity.
    */
   public function validateForm(array &$form, FormStateInterface $form_state);

+ 48 - 2
web/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php

@@ -14,8 +14,54 @@ use Drupal\Core\Site\Settings;
 /**
  * Provides an entity autocomplete form element.
  *
- * The #default_value accepted by this element is either an entity object or an
- * array of entity objects.
+ * The autocomplete form element allows users to select one or multiple
+ * entities, which can come from all or specific bundles of an entity type.
+ *
+ * Properties:
+ * - #target_type: (required) The ID of the target entity type.
+ * - #tags: (optional) TRUE if the element allows multiple selection. Defaults
+ *   to FALSE.
+ * - #default_value: (optional) The default entity or an array of default
+ *   entities, depending on the value of #tags.
+ * - #selection_handler: (optional) The plugin ID of the entity reference
+ *   selection handler (a plugin of type EntityReferenceSelection). The default
+ *   value is the lowest-weighted plugin that is compatible with #target_type.
+ * - #selection_settings: (optional) An array of settings for the selection
+ *   handler. Settings for the default selection handler
+ *   \Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection are:
+ *   - target_bundles: Array of bundles to allow (omit to allow all bundles).
+ *   - sort: Array with 'field' and 'direction' keys, determining how results
+ *     will be sorted. Defaults to unsorted.
+ * - #autocreate: (optional) Array of settings used to auto-create entities
+ *   that do not exist (omit to not auto-create entities). Elements:
+ *   - bundle: (required) Bundle to use for auto-created entities.
+ *   - uid: User ID to use as the author of auto-created entities. Defaults to
+ *     the current user.
+ * - #process_default_value: (optional) Set to FALSE if the #default_value
+ *   property is processed and access checked elsewhere (such as by a Field API
+ *   widget). Defaults to TRUE.
+ * - #validate_reference: (optional) Set to FALSE if validation of the selected
+ *   entities is performed elsewhere. Defaults to TRUE.
+ *
+ * Usage example:
+ * @code
+ * $form['my_element'] = [
+ *  '#type' => 'entity_autocomplete',
+ *  '#target_type' => 'node',
+ *  '#tags' => TRUE,
+ *  '#default_value' => $node,
+ *  '#selection_handler' => 'default',
+ *  '#selection_settings' => [
+ *    'target_bundles' => ['article', 'page'],
+ *   ],
+ *  '#autocreate' => [
+ *    'bundle' => 'article',
+ *    'uid' => <a valid user ID>,
+ *   ],
+ * ];
+ * @endcode
+ *
+ * @see \Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection
  *
  * @FormElement("entity_autocomplete")
  */

+ 9 - 6
web/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/DefaultSelection.php

@@ -395,13 +395,16 @@ class DefaultSelection extends SelectionPluginBase implements ContainerFactoryPl
    */
   public function createNewEntity($entity_type_id, $bundle, $label, $uid) {
     $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
-    $bundle_key = $entity_type->getKey('bundle');
-    $label_key = $entity_type->getKey('label');
 
-    $entity = $this->entityTypeManager->getStorage($entity_type_id)->create([
-      $bundle_key => $bundle,
-      $label_key => $label,
-    ]);
+    $values = [
+      $entity_type->getKey('label') => $label,
+    ];
+
+    if ($bundle_key = $entity_type->getKey('bundle')) {
+      $values[$bundle_key] = $bundle;
+    }
+
+    $entity = $this->entityTypeManager->getStorage($entity_type_id)->create($values);
 
     if ($entity instanceof EntityOwnerInterface) {
       $entity->setOwnerId($uid);

+ 13 - 2
web/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php

@@ -846,8 +846,19 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
 
     // Add the bundle column.
     if ($bundle = $this->entityType->getKey('bundle')) {
-      if ($base_table) {
-        $select->join($base_table, 'base_table', "entity_table.{$this->entityType->getKey('id')} = %alias.{$this->entityType->getKey('id')}");
+      // The bundle field is not stored in the revision table, so we need to
+      // join the data (or base) table and retrieve it from there.
+      if ($base_table && $base_table !== $table_name) {
+        $join_condition = "entity_table.{$this->entityType->getKey('id')} = %alias.{$this->entityType->getKey('id')}";
+
+        // If the entity type is translatable, we also need to add the langcode
+        // to the join, otherwise we'll get duplicate rows for each language.
+        if ($this->entityType->isTranslatable()) {
+          $langcode = $this->entityType->getKey('langcode');
+          $join_condition .= " AND entity_table.{$langcode} = %alias.{$langcode}";
+        }
+
+        $select->join($base_table, 'base_table', $join_condition);
         $select->addField('base_table', $bundle, 'bundle');
       }
       else {

+ 20 - 2
web/core/lib/Drupal/Core/Entity/entity.api.php

@@ -616,6 +616,18 @@ use Drupal\node\Entity\NodeType;
  * are invoked are hook_entity_create_access() and
  * hook_ENTITY_TYPE_create_access() instead.
  *
+ * The access to an entity can be influenced in several ways:
+ * - To explicitly allow access, return an AccessResultInterface object with
+ * isAllowed() returning TRUE. Other modules can override this access by
+ * returning TRUE for isForbidden().
+ * - To explicitly forbid access, return an AccessResultInterface object with
+ * isForbidden() returning TRUE. Access will be forbidden even if your module
+ * (or another module) also returns TRUE for isNeutral() or isAllowed().
+ * - To neither allow nor explicitly forbid access, return an
+ * AccessResultInterface object with isNeutral() returning TRUE.
+ * - If your module does not return an AccessResultInterface object, neutral
+ * access will be assumed.
+ *
  * The Node entity type has a complex system for determining access, which
  * developers can interact with. This is described in the
  * @link node_access Node access topic. @endlink
@@ -643,7 +655,10 @@ use Drupal\node\Entity\NodeType;
  * @param string $operation
  *   The operation that is to be performed on $entity.
  * @param \Drupal\Core\Session\AccountInterface $account
- *   The account trying to access the entity.
+ *   The account trying to access the entity. Usually one of:
+ *   - "view"
+ *   - "update"
+ *   - "delete"
  *
  * @return \Drupal\Core\Access\AccessResultInterface
  *   The access result. The final result is calculated by using
@@ -677,7 +692,10 @@ function hook_entity_access(\Drupal\Core\Entity\EntityInterface $entity, $operat
  * @param \Drupal\Core\Entity\EntityInterface $entity
  *   The entity to check access to.
  * @param string $operation
- *   The operation that is to be performed on $entity.
+ *   The operation that is to be performed on $entity. Usually one of:
+ *   - "view"
+ *   - "update"
+ *   - "delete"
  * @param \Drupal\Core\Session\AccountInterface $account
  *   The account trying to access the entity.
  *

+ 1 - 0
web/core/lib/Drupal/Core/EventSubscriber/ExceptionLoggingSubscriber.php

@@ -42,6 +42,7 @@ class ExceptionLoggingSubscriber implements EventSubscriberInterface {
     // why access was denied.
     $exception = $event->getException();
     $error = Error::decodeException($exception);
+    unset($error['@backtrace_string']);
     $error['@uri'] = $event->getRequest()->getRequestUri();
     $this->logger->get('access denied')->warning('Path: @uri. %type: @message in %function (line %line of %file).', $error);
   }

+ 1 - 1
web/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/BooleanCheckboxWidget.php

@@ -49,7 +49,7 @@ class BooleanCheckboxWidget extends WidgetBase {
     $summary = [];
 
     $display_label = $this->getSetting('display_label');
-    $summary[] = t('Use field label: @display_label', ['@display_label' => ($display_label ? t('Yes') : 'No')]);
+    $summary[] = t('Use field label: @display_label', ['@display_label' => ($display_label ? t('Yes') : t('No'))]);
 
     return $summary;
   }

+ 12 - 5
web/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/EntityReferenceAutocompleteWidget.php

@@ -117,7 +117,7 @@ class EntityReferenceAutocompleteWidget extends WidgetBase {
       '#placeholder' => $this->getSetting('placeholder'),
     ];
 
-    if ($this->getSelectionHandlerSetting('auto_create') && ($bundle = $this->getAutocreateBundle())) {
+    if ($bundle = $this->getAutocreateBundle()) {
       $element['#autocreate'] = [
         'bundle' => $bundle,
         'uid' => ($entity instanceof EntityOwnerInterface) ? $entity->getOwnerId() : \Drupal::currentUser()->id(),
@@ -154,16 +154,23 @@ class EntityReferenceAutocompleteWidget extends WidgetBase {
    * Returns the name of the bundle which will be used for autocreated entities.
    *
    * @return string
-   *   The bundle name.
+   *   The bundle name. If autocreate is not active, NULL will be returned.
    */
   protected function getAutocreateBundle() {
     $bundle = NULL;
-    if ($this->getSelectionHandlerSetting('auto_create') && $target_bundles = $this->getSelectionHandlerSetting('target_bundles')) {
+    if ($this->getSelectionHandlerSetting('auto_create')) {
+      $target_bundles = $this->getSelectionHandlerSetting('target_bundles');
+      // If there's no target bundle at all, use the target_type. It's the
+      // default for bundleless entity types.
+      if (empty($target_bundles)) {
+        $bundle = $this->getFieldSetting('target_type');
+      }
       // If there's only one target bundle, use it.
-      if (count($target_bundles) == 1) {
+      elseif (count($target_bundles) == 1) {
         $bundle = reset($target_bundles);
       }
-      // Otherwise use the target bundle stored in selection handler settings.
+      // If there's more than one target bundle, use the autocreate bundle
+      // stored in selection handler settings.
       elseif (!$bundle = $this->getSelectionHandlerSetting('auto_create_bundle')) {
         // If no bundle has been set as auto create target means that there is
         // an inconsistency in entity reference field settings.

+ 2 - 1
web/core/lib/Drupal/Core/Form/FormBuilder.php

@@ -861,7 +861,8 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface, FormS
     //   https://www.drupal.org/node/2504709.
     $parsed = UrlHelper::parse($request_uri);
     unset($parsed['query'][static::AJAX_FORM_REQUEST], $parsed['query'][MainContentViewSubscriber::WRAPPER_FORMAT]);
-    return $parsed['path'] . ($parsed['query'] ? ('?' . UrlHelper::buildQuery($parsed['query'])) : '');
+    $action = $parsed['path'] . ($parsed['query'] ? ('?' . UrlHelper::buildQuery($parsed['query'])) : '');
+    return UrlHelper::filterBadProtocol($action);
   }
 
   /**

+ 2 - 2
web/core/lib/Drupal/Core/Link.php

@@ -66,8 +66,8 @@ class Link implements RenderableInterface {
   /**
    * Creates a Link object from a given Url object.
    *
-   * @param string $text
-   *   The text of the link.
+   * @param string|array|\Drupal\Component\Render\MarkupInterface $text
+   *   The link text for the anchor tag as a translated string or render array.
    * @param \Drupal\Core\Url $url
    *   The Url to create the link for.
    *

+ 2 - 2
web/core/lib/Drupal/Core/Logger/RfcLogLevel.php

@@ -10,13 +10,13 @@ use Drupal\Core\StringTranslation\TranslatableMarkup;
  * Logging severity levels as defined in RFC 5424.
  *
  * The constant definitions of this class correspond to the logging severity
- * levels defined in RFC 5424, section 4.1.1. PHP supplies predefined LOG_*
+ * levels defined in RFC 5424, section 6.2.1. PHP supplies predefined LOG_*
  * constants for use in the syslog() function, but their values on Windows
  * builds do not correspond to RFC 5424. The associated PHP bug report was
  * closed with the comment, "And it's also not a bug, as Windows just have less
  * log levels," and "So the behavior you're seeing is perfectly normal."
  *
- * @see http://tools.ietf.org/html/rfc5424
+ * @see https://tools.ietf.org/html/rfc5424#section-6.2.1
  * @see http://bugs.php.net/bug.php?id=18090
  * @see http://php.net/manual/function.syslog.php
  * @see http://php.net/manual/network.constants.php

+ 4 - 4
web/core/lib/Drupal/Core/Render/Element/Email.php

@@ -15,14 +15,14 @@ use Drupal\Core\Render\Element;
  *
  * Example usage:
  * @code
- * $form['email'] = array(
+ * $form['email'] = [
  *   '#type' => 'email',
  *   '#title' => $this->t('Email'),
  *   '#pattern' => '*@example.com',
- * );
- * @end
+ * ];
+ * @endcode
  *
- * @see \Drupal\Core\Render\Element\Render\Textfield
+ * @see \Drupal\Core\Render\Element\Textfield
  *
  * @FormElement("email")
  */

+ 1 - 1
web/core/lib/Drupal/Core/Render/Element/StatusReport.php

@@ -52,7 +52,7 @@ class StatusReport extends RenderElement {
     // Order the grouped requirements by a set order.
     $order = array_flip($element['#priorities']);
     uksort($grouped_requirements, function ($a, $b) use ($order) {
-      return $order[$a] > $order[$b];
+      return $order[$a] <=> $order[$b];
     });
 
     $element['#grouped_requirements'] = $grouped_requirements;

+ 6 - 3
web/core/lib/Drupal/Core/Render/theme.api.php

@@ -414,13 +414,16 @@
  * render array contained:
  * @code
  * $build['my_element'] = [
- *   '#attached' => ['placeholders' => ['@foo' => 'replacement']],
- *   '#markup' => ['Something about @foo'],
+ *   '#markup' => 'Something about @foo',
+ *   '#attached' => [
+ *     'placeholders' => [
+ *       '@foo' => ['#markup' => 'replacement'],
+ *     ],
  * ];
  * @endcode
  * then #markup would end up containing 'Something about replacement'.
  *
- * Note that each placeholder value can itself be a render array, which will be
+ * Note that each placeholder value *must* itself be a render array. It will be
  * rendered, and any cache tags generated during rendering will be added to the
  * cache tags for the markup.
  *

+ 2 - 2
web/core/lib/Drupal/Core/Security/PharExtensionInterceptor.php

@@ -60,8 +60,8 @@ class PharExtensionInterceptor implements Assertable {
       return FALSE;
     }
     // If the stream wrapper is registered by invoking a phar file that does
-    // not not have .phar extension then this should be allowed. For
-    // example, some CLI tools recommend removing the extension.
+    // not have .phar extension then this should be allowed. For example, some
+    // CLI tools recommend removing the extension.
     $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
     // Find the last entry in the backtrace containing a 'file' key as
     // sometimes the last caller is executed outside the scope of a file. For

+ 1 - 1
web/core/lib/Drupal/Core/Template/AttributeHelper.php

@@ -26,7 +26,7 @@ class AttributeHelper {
    *   An Attribute object or an array of attributes.
    *
    * @return bool
-   *   TRUE if the attibute exists, FALSE otherwise.
+   *   TRUE if the attribute exists, FALSE otherwise.
    *
    * @throws \InvalidArgumentException
    *   When the input $collection is neither an Attribute object nor an array.

+ 2 - 2
web/core/lib/Drupal/Core/Test/PhpUnitTestRunner.php

@@ -205,7 +205,7 @@ class PhpUnitTestRunner implements ContainerInjectionInterface {
    */
   public function runTests($test_id, array $unescaped_test_classnames, &$status = NULL) {
     $phpunit_file = $this->xmlLogFilePath($test_id);
-    // Store ouptut from our test run.
+    // Store output from our test run.
     $output = [];
     $this->runCommand($unescaped_test_classnames, $phpunit_file, $status, $output);
 
@@ -217,7 +217,7 @@ class PhpUnitTestRunner implements ContainerInjectionInterface {
         'test_id' => $test_id,
         'test_class' => implode(",", $unescaped_test_classnames),
         'status' => TestStatus::label($status),
-        'message' => 'PHPunit Test failed to complete; Error: ' . implode("\n", $output),
+        'message' => 'PHPUnit Test failed to complete; Error: ' . implode("\n", $output),
         'message_group' => 'Other',
         'function' => implode(",", $unescaped_test_classnames),
         'line' => '0',

+ 9 - 1
web/core/lib/Drupal/Core/TypedData/TypedDataManager.php

@@ -169,7 +169,15 @@ class TypedDataManager extends DefaultPluginManager implements TypedDataManagerI
       $parts[] = json_encode($settings);
     }
     // Property path for the requested data object.
-    $parts[] = $object->getPropertyPath() . '.' . $property_name;
+    $parts[] = $object->getPropertyPath();
+    // Only property instances of complex data types should be cached by the
+    // property name, as they represent different properties. Properties of list
+    // data types are the items of the list and the property name represents
+    // only the delta in that list and not an unique property, which is why all
+    // items should use the same prototype.
+    if ($object instanceof ComplexDataInterface) {
+      $parts[] = $property_name;
+    }
     $key = implode(':', $parts);
 
     // Create the prototype if needed.

+ 40 - 6
web/core/lib/Drupal/Core/Update/UpdateCompilerPass.php

@@ -27,16 +27,28 @@ class UpdateCompilerPass implements CompilerPassInterface {
     do {
       $has_changed = FALSE;
       foreach ($container->getDefinitions() as $key => $definition) {
+        // Ensure all the definition's arguments are valid.
         foreach ($definition->getArguments() as $argument) {
-          if ($argument instanceof Reference) {
-            $argument_id = (string) $argument;
-            if (!$container->has($argument_id) && $argument->getInvalidBehavior() === ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
-              // If the container does not have the argument and would throw an
-              // exception, remove the service.
+          if ($this->isArgumentMissingService($argument, $container)) {
+            $container->removeDefinition($key);
+            $container->log($this, sprintf('Removed service "%s"; reason: depends on non-existent service "%s".', $key, (string) $argument));
+            $has_changed = TRUE;
+            $process_aliases = TRUE;
+            // Process the next definition.
+            continue 2;
+          }
+        }
+
+        // Ensure all the method call arguments are valid.
+        foreach ($definition->getMethodCalls() as $call) {
+          foreach ($call[1] as $argument) {
+            if ($this->isArgumentMissingService($argument, $container)) {
               $container->removeDefinition($key);
-              $container->log($this, sprintf('Removed service "%s"; reason: depends on non-existent service "%s".', $key, $argument_id));
+              $container->log($this, sprintf('Removed service "%s"; reason: method call "%s" depends on non-existent service "%s".', $key, $call[0], (string) $argument));
               $has_changed = TRUE;
               $process_aliases = TRUE;
+              // Process the next definition.
+              continue 3;
             }
           }
         }
@@ -58,4 +70,26 @@ class UpdateCompilerPass implements CompilerPassInterface {
     }
   }
 
+  /**
+   * Checks if a reference argument is to a missing service.
+   *
+   * @param mixed $argument
+   *   The argument to check.
+   * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
+   *   The container.
+   *
+   * @return bool
+   *   TRUE if the argument is a reference to a service that is missing from the
+   *   container and the reference is required, FALSE if not.
+   */
+  private function isArgumentMissingService($argument, ContainerBuilder $container) {
+    if ($argument instanceof Reference) {
+      $argument_id = (string) $argument;
+      if (!$container->has($argument_id) && $argument->getInvalidBehavior() === ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
+        return TRUE;
+      }
+    }
+    return FALSE;
+  }
+
 }

+ 1 - 1
web/core/lib/Drupal/Core/Url.php

@@ -297,7 +297,7 @@ class Url implements TrustedCallbackInterface {
     // Extract query parameters and fragment and merge them into $uri_options,
     // but preserve the original $options for the fallback case.
     $uri_options = $options;
-    if (isset($uri_parts['fragment'])) {
+    if (isset($uri_parts['fragment']) && $uri_parts['fragment'] !== '') {
       $uri_options += ['fragment' => $uri_parts['fragment']];
       unset($uri_parts['fragment']);
     }

+ 8 - 8
web/core/lib/Drupal/Core/Utility/ProjectInfo.php

@@ -35,11 +35,11 @@ class ProjectInfo {
    * @param bool $status
    *   Boolean that controls what status (enabled or uninstalled) to process out
    *   of the $list and add to the $projects array.
-   * @param array $additional_whitelist
+   * @param array $additional_elements
    *   (optional) Array of additional elements to be collected from the .info.yml
    *   file. Defaults to array().
    */
-  public function processInfoList(array &$projects, array $list, $project_type, $status, array $additional_whitelist = []) {
+  public function processInfoList(array &$projects, array $list, $project_type, $status, array $additional_elements = []) {
     foreach ($list as $file) {
       // Just projects with a matching status should be listed.
       if ($file->status != $status) {
@@ -111,7 +111,7 @@ class ProjectInfo {
           'name' => $project_name,
           // Only save attributes from the .info.yml file we care about so we do
           // not bloat our RAM usage needlessly.
-          'info' => $this->filterProjectInfo($file->info, $additional_whitelist),
+          'info' => $this->filterProjectInfo($file->info, $additional_elements),
           'datestamp' => $file->info['datestamp'],
           'includes' => [$file->getName() => $file->info['name']],
           'project_type' => $project_display_type,
@@ -165,7 +165,7 @@ class ProjectInfo {
    * @param array $info
    *   Array of .info.yml file data as returned by
    *   \Drupal\Core\Extension\InfoParser.
-   * @param $additional_whitelist
+   * @param $additional_elements
    *   (optional) Array of additional elements to be collected from the .info.yml
    *   file. Defaults to array().
    *
@@ -174,8 +174,8 @@ class ProjectInfo {
    *
    * @see \Drupal\Core\Utility\ProjectInfo::processInfoList()
    */
-  public function filterProjectInfo($info, $additional_whitelist = []) {
-    $whitelist = [
+  public function filterProjectInfo($info, $additional_elements = []) {
+    $elements = [
       '_info_file_ctime',
       'datestamp',
       'major',
@@ -185,8 +185,8 @@ class ProjectInfo {
       'project status url',
       'version',
     ];
-    $whitelist = array_merge($whitelist, $additional_whitelist);
-    return array_intersect_key($info, array_combine($whitelist, $whitelist));
+    $elements = array_merge($elements, $additional_elements);
+    return array_intersect_key($info, array_combine($elements, $elements));
   }
 
 }

+ 1 - 0
web/core/misc/ajax.es6.js

@@ -559,6 +559,7 @@
         }
       },
       dataType: 'json',
+      jsonp: false,
       type: 'POST',
     };
 

+ 1 - 0
web/core/misc/ajax.js

@@ -243,6 +243,7 @@ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr
       },
 
       dataType: 'json',
+      jsonp: false,
       type: 'POST'
     };
 

+ 1 - 0
web/core/misc/autocomplete.es6.js

@@ -281,6 +281,7 @@
     },
     ajax: {
       dataType: 'json',
+      jsonp: false,
     },
   };
 

+ 2 - 1
web/core/misc/autocomplete.js

@@ -156,7 +156,8 @@
       isComposing: false
     },
     ajax: {
-      dataType: 'json'
+      dataType: 'json',
+      jsonp: false
     }
   };
 

+ 2 - 5
web/core/modules/aggregator/tests/src/Functional/AggregatorTestBase.php

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

+ 4 - 4
web/core/modules/aggregator/tests/src/Functional/FeedParserTest.php

@@ -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.');
   }
 

+ 3 - 3
web/core/modules/aggregator/tests/src/Functional/UpdateFeedItemTest.php

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

+ 2 - 2
web/core/modules/ban/tests/src/Functional/IpAddressBlockingTest.php

@@ -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.');
 

+ 1 - 1
web/core/modules/big_pipe/src/Render/Placeholder/BigPipeStrategy.php

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

+ 19 - 18
web/core/modules/big_pipe/tests/src/Functional/BigPipeTest.php

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

+ 0 - 3
web/core/modules/block/tests/src/Kernel/BlockViewBuilderTest.php

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

+ 6 - 6
web/core/modules/block/tests/src/Unit/Plugin/migrate/process/BlockVisibilityTest.php

@@ -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');
   }
 
 }

+ 1 - 1
web/core/modules/block_content/src/Plugin/migrate/source/d7/BlockCustomTranslation.php

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

+ 1 - 1
web/core/modules/block_content/tests/src/Functional/BlockContentCreationTest.php

@@ -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();

+ 1 - 3
web/core/modules/block_content/tests/src/Functional/BlockContentTranslationUITest.php

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

+ 4 - 9
web/core/modules/block_content/tests/src/Kernel/Migrate/MigrateBlockContentStubTest.php

@@ -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');
   }
 
   /**

+ 2 - 3
web/core/modules/book/src/Form/BookSettingsForm.php

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

+ 1 - 1
web/core/modules/book/tests/src/Functional/BookTestTrait.php

@@ -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]));

+ 5 - 0
web/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.es6.js

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

+ 3 - 0
web/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js

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

+ 1 - 1
web/core/modules/ckeditor/src/CKEditorPluginManager.php

@@ -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']);
       }, []);
     }

+ 2 - 1
web/core/modules/color/tests/src/Functional/ColorTest.php

@@ -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
web/core/modules/comment/comment.module

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

+ 3 - 3
web/core/modules/comment/src/CommentLazyBuilders.php

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

+ 10 - 6
web/core/modules/comment/src/CommentViewBuilder.php

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

+ 1 - 1
web/core/modules/comment/src/CommentViewsData.php

@@ -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' => [

+ 4 - 3
web/core/modules/comment/src/Entity/Comment.php

@@ -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');
     }
   }
 

+ 1 - 1
web/core/modules/comment/src/Plugin/views/field/NodeNewComments.php

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

+ 8 - 0
web/core/modules/comment/tests/modules/comment_base_field_test/comment_base_field_test.info.yml

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

+ 6 - 0
web/core/modules/comment/tests/modules/comment_base_field_test/config/install/comment.type.test_comment_type.yml

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

+ 39 - 0
web/core/modules/comment/tests/modules/comment_base_field_test/src/Entity/CommentTestBaseField.php

@@ -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;
+  }
+
+}

+ 244 - 0
web/core/modules/comment/tests/modules/comment_test_views/test_views/views.view.test_comment_count.yml

@@ -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: {  }

+ 2 - 1
web/core/modules/comment/tests/src/Functional/CommentAnonymousTest.php

@@ -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.');
 

+ 1 - 1
web/core/modules/comment/tests/src/Functional/CommentInterfaceTest.php

@@ -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();

+ 2 - 1
web/core/modules/comment/tests/src/Functional/CommentNonNodeTest.php

@@ -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.');

+ 0 - 1
web/core/modules/comment/tests/src/Functional/CommentPagerTest.php

@@ -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');

+ 3 - 2
web/core/modules/comment/tests/src/Functional/CommentTitleTest.php

@@ -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');

+ 1 - 1
web/core/modules/comment/tests/src/Functional/CommentTypeTest.php

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

+ 27 - 1
web/core/modules/comment/tests/src/Functional/Views/NodeCommentsTest.php

@@ -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);
+  }
+
 }

+ 64 - 0
web/core/modules/comment/tests/src/Kernel/CommentBaseFieldTest.php

@@ -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());
+  }
+
+}

+ 134 - 0
web/core/modules/comment/tests/src/Kernel/CommentOrphanTest.php

@@ -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'],
+    ];
+  }
+
+}

+ 17 - 22
web/core/modules/comment/tests/src/Kernel/CommentStringIdEntitiesTest.php

@@ -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();
   }
 
 }

+ 1 - 0
web/core/modules/comment/tests/src/Kernel/Migrate/d7/MigrateCommentFieldInstanceTest.php

@@ -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);
   }
 
 }

+ 1 - 0
web/core/modules/comment/tests/src/Kernel/Migrate/d7/MigrateCommentFieldTest.php

@@ -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');
   }
 
 }

+ 1 - 1
web/core/modules/config/src/Form/ConfigSingleImportForm.php

@@ -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');
 

+ 1 - 1
web/core/modules/config/tests/config_schema_test/config_schema_test.module

@@ -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']);

+ 5 - 15
web/core/modules/config/tests/src/Functional/ConfigEntityTest.php

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

+ 4 - 0
web/core/modules/config/tests/src/Functional/ConfigSingleImportExportTest.php

@@ -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.');
   }
 
   /**

+ 3 - 9
web/core/modules/config/tests/src/Functional/SchemaConfigListenerWebTest.php

@@ -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());
     }
 

+ 4 - 0
web/core/modules/content_moderation/src/Plugin/views/ModerationStateJoinViewsHandlerTrait.php

@@ -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()) {

+ 12 - 3
web/core/modules/content_moderation/src/Plugin/views/filter/ModerationStateFilter.php

@@ -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);
+    }
   }
 
   /**

+ 348 - 0
web/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_filter_via_relationship.yml

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

+ 268 - 0
web/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_state_filter_base_table_filter_group_or.yml

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

+ 1 - 1
web/core/modules/content_moderation/tests/src/Kernel/ContentModerationAccessTest.php

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

Деякі файли не було показано, через те що забагато файлів було змінено