summaryrefslogtreecommitdiffstatshomepage
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/.phpstan-baseline.php12
-rw-r--r--core/assets/scaffold/files/htaccess2
-rw-r--r--core/lib/Drupal/Core/Extension/ModuleHandler.php4
-rw-r--r--core/lib/Drupal/Core/Hook/HookCollectorPass.php8
-rw-r--r--core/modules/ckeditor5/tests/src/Unit/CKEditor5ImageControllerTest.php85
-rw-r--r--core/modules/file/tests/file_test/src/Form/FileRequiredTestForm.php4
-rw-r--r--core/modules/file/tests/file_test/src/Form/FileTestForm.php15
-rw-r--r--core/modules/jsonapi/jsonapi.api.php17
-rw-r--r--core/modules/jsonapi/jsonapi.module16
-rw-r--r--core/modules/jsonapi/src/Access/TemporaryQueryGuard.php31
-rw-r--r--core/modules/jsonapi/src/Hook/JsonapiHooks.php59
-rw-r--r--core/modules/jsonapi/src/JsonApiFilter.php77
-rw-r--r--core/modules/layout_builder/src/InlineBlockEntityOperations.php2
-rw-r--r--core/modules/node/node.module4
-rw-r--r--core/modules/system/tests/fixtures/HtaccessTest/access_test.css6
-rw-r--r--core/modules/system/tests/fixtures/HtaccessTest/access_test.less0
-rw-r--r--core/modules/system/tests/fixtures/HtaccessTest/access_test.pcss0
-rw-r--r--core/modules/system/tests/fixtures/HtaccessTest/access_test.pcss.css1
-rw-r--r--core/modules/system/tests/fixtures/HtaccessTest/access_test.sass0
-rw-r--r--core/modules/system/tests/fixtures/HtaccessTest/access_test.scss0
-rw-r--r--core/modules/system/tests/modules/container_initialize/container_initialize.info.yml5
-rw-r--r--core/modules/system/tests/modules/container_initialize/container_initialize.module10
-rw-r--r--core/modules/system/tests/src/Functional/Hook/HookCollectorPassTest.php61
-rw-r--r--core/modules/system/tests/src/Functional/System/HtaccessTest.php5
-rw-r--r--core/tests/Drupal/BuildTests/Composer/Plugin/Unpack/Functional/UnpackRecipeTest.php (renamed from core/tests/Drupal/Tests/Composer/Plugin/Unpack/Functional/UnpackRecipeTest.php)2
25 files changed, 339 insertions, 87 deletions
diff --git a/core/.phpstan-baseline.php b/core/.phpstan-baseline.php
index e0660c7dc93e..4b4b01e4701b 100644
--- a/core/.phpstan-baseline.php
+++ b/core/.phpstan-baseline.php
@@ -17462,18 +17462,6 @@ $ignoreErrors[] = [
'path' => __DIR__ . '/modules/file/tests/file_module_test/src/Form/FileModuleTestForm.php',
];
$ignoreErrors[] = [
- 'message' => '#^Method Drupal\\\\file_test\\\\Form\\\\FileTestForm\\:\\:submitForm\\(\\) has no return type specified\\.$#',
- 'identifier' => 'missingType.return',
- 'count' => 1,
- 'path' => __DIR__ . '/modules/file/tests/file_test/src/Form/FileTestForm.php',
-];
-$ignoreErrors[] = [
- 'message' => '#^Method Drupal\\\\file_test\\\\Form\\\\FileTestForm\\:\\:validateForm\\(\\) has no return type specified\\.$#',
- 'identifier' => 'missingType.return',
- 'count' => 1,
- 'path' => __DIR__ . '/modules/file/tests/file_test/src/Form/FileTestForm.php',
-];
-$ignoreErrors[] = [
'message' => '#^Method Drupal\\\\file_test\\\\Form\\\\FileTestSaveUploadFromForm\\:\\:create\\(\\) has no return type specified\\.$#',
'identifier' => 'missingType.return',
'count' => 1,
diff --git a/core/assets/scaffold/files/htaccess b/core/assets/scaffold/files/htaccess
index 1ac01a117b27..fe31ae517672 100644
--- a/core/assets/scaffold/files/htaccess
+++ b/core/assets/scaffold/files/htaccess
@@ -3,7 +3,7 @@
#
# Protect files and directories from prying eyes.
-<FilesMatch "\.(engine|inc|install|make|module|profile|po|sh|.*sql|theme|twig|tpl(\.php)?|xtmpl|yml)(~|\.sw[op]|\.bak|\.orig|\.save)?$|^(\.(?!well-known).*|Entries.*|Repository|Root|Tag|Template|composer\.(json|lock)|web\.config|yarn\.lock|package\.json)$|^#.*#$|\.php(~|\.sw[op]|\.bak|\.orig|\.save)$">
+<FilesMatch "\.(engine|inc|install|make|module|profile|po|sh|scss|sass|less|pcss|pcss\.css|.*sql|theme|twig|tpl(\.php)?|xtmpl|yml)(~|\.sw[op]|\.bak|\.orig|\.save)?$|^(\.(?!well-known).*|Entries.*|Repository|Root|Tag|Template|composer\.(json|lock)|web\.config|yarn\.lock|package\.json)$|^#.*#$|\.php(~|\.sw[op]|\.bak|\.orig|\.save)$">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index ad361d3fe669..b187560c10fb 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -230,7 +230,9 @@ class ModuleHandler implements ModuleHandlerInterface {
protected function add($type, $name, $path) {
$pathname = "$path/$name.info.yml";
$php_file_path = $this->root . "/$path/$name.$type";
- $filename = file_exists($php_file_path) ? "$name.$type" : NULL;
+ if ($filename = file_exists($php_file_path) ? "$name.$type" : NULL) {
+ include_once $php_file_path;
+ }
$this->moduleList[$name] = new Extension($this->root, $type, $pathname, $filename);
$this->resetImplementations();
$hook_collector = HookCollectorPass::collectAllHookImplementations([$name => ['pathname' => $pathname]]);
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 7c0f913ca219..75bbb039414e 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -403,12 +403,6 @@ class HookCollectorPass implements CompilerPassInterface {
$extension = $fileinfo->getExtension();
$filename = $fileinfo->getPathname();
- if (($extension === 'module' || $extension === 'profile') && !$iterator->getDepth() && !$skip_procedural) {
- // There is an expectation for all modules and profiles to be loaded.
- // .module and .profile files are not supposed to be in subdirectories.
- // These need to be loaded even if the module has no procedural hooks.
- include_once $filename;
- }
if ($extension === 'php') {
$cached = $hook_file_cache->get($filename);
if ($cached) {
@@ -512,11 +506,13 @@ class HookCollectorPass implements CompilerPassInterface {
$function = $module . '_' . $hook;
if ($hook === 'hook_info') {
$this->hookInfo[] = $function;
+ include_once $fileinfo->getPathname();
}
elseif ($hook === 'module_implements_alter') {
$message = "$function without a #[LegacyModuleImplementsAlter] attribute is deprecated in drupal:11.2.0 and removed in drupal:12.0.0. See https://www.drupal.org/node/3496788";
@trigger_error($message, E_USER_DEPRECATED);
$this->moduleImplementsAlters[] = $function;
+ include_once $fileinfo->getPathname();
}
$this->proceduralImplementations[$hook][] = $module;
if ($fileinfo->getExtension() !== 'module') {
diff --git a/core/modules/ckeditor5/tests/src/Unit/CKEditor5ImageControllerTest.php b/core/modules/ckeditor5/tests/src/Unit/CKEditor5ImageControllerTest.php
new file mode 100644
index 000000000000..bb69c28c6fbf
--- /dev/null
+++ b/core/modules/ckeditor5/tests/src/Unit/CKEditor5ImageControllerTest.php
@@ -0,0 +1,85 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\ckeditor5\Unit;
+
+use Drupal\ckeditor5\Controller\CKEditor5ImageController;
+use Drupal\ckeditor5\Plugin\CKEditor5PluginManagerInterface;
+use Drupal\Core\Entity\EntityConstraintViolationList;
+use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Entity\EntityTypeRepositoryInterface;
+use Drupal\Core\File\FileSystemInterface;
+use Drupal\Core\Lock\LockBackendInterface;
+use Drupal\editor\EditorInterface;
+use Drupal\file\Entity\File;
+use Drupal\file\FileInterface;
+use Drupal\file\Upload\FileUploadHandlerInterface;
+use Drupal\Tests\UnitTestCase;
+use Prophecy\Argument;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Exception\HttpException;
+
+/**
+ * Tests CKEditor5ImageController.
+ *
+ * @group ckeditor5
+ * @coversDefaultClass \Drupal\ckeditor5\Controller\CKEditor5ImageController
+ */
+final class CKEditor5ImageControllerTest extends UnitTestCase {
+
+ /**
+ * Tests that upload fails correctly when the file is too large.
+ */
+ public function testInvalidFile(): void {
+ $file_system = $this->prophesize(FileSystemInterface::class);
+ $file_system->move(Argument::any())->shouldNotBeCalled();
+ $directory = 'public://';
+ $file_system->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY)->willReturn(TRUE);
+ $file_system->getDestinationFilename(Argument::cetera())->willReturn('/tmp/foo.txt');
+ $lock = $this->prophesize(LockBackendInterface::class);
+ $lock->acquire(Argument::any())->willReturn(TRUE);
+ $container = $this->prophesize(ContainerInterface::class);
+ $file_storage = $this->prophesize(EntityStorageInterface::class);
+ $file = $this->prophesize(FileInterface::class);
+ $violations = $this->prophesize(EntityConstraintViolationList::class);
+ $violations->count()->willReturn(0);
+ $file->validate()->willReturn($violations->reveal());
+ $file_storage->create(Argument::any())->willReturn($file->reveal());
+ $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
+ $entity_type_manager->getStorage('file')->willReturn($file_storage->reveal());
+ $container->get('entity_type.manager')->willReturn($entity_type_manager->reveal());
+ $entity_type_repository = $this->prophesize(EntityTypeRepositoryInterface::class);
+ $entity_type_repository->getEntityTypeFromClass(File::class)->willReturn('file');
+ $container->get('entity_type.repository')->willReturn($entity_type_repository->reveal());
+ \Drupal::setContainer($container->reveal());
+ $controller = new CKEditor5ImageController(
+ $file_system->reveal(),
+ $this->prophesize(FileUploadHandlerInterface::class)->reveal(),
+ $lock->reveal(),
+ $this->prophesize(CKEditor5PluginManagerInterface::class)->reveal(),
+ );
+ // We can't use vfsstream here because of how Symfony request works.
+ $file_uri = tempnam(sys_get_temp_dir(), 'tmp');
+ $fp = fopen($file_uri, 'w');
+ fwrite($fp, 'foo');
+ fclose($fp);
+ $request = Request::create('/', files: [
+ 'upload' => [
+ 'name' => 'foo.txt',
+ 'type' => 'text/plain',
+ 'size' => 42,
+ 'tmp_name' => $file_uri,
+ 'error' => \UPLOAD_ERR_FORM_SIZE,
+ ],
+ ]);
+ $editor = $this->prophesize(EditorInterface::class);
+ $request->attributes->set('editor', $editor->reveal());
+ $this->expectException(HttpException::class);
+ $this->expectExceptionMessage('The file "foo.txt" exceeds the upload limit defined in your form.');
+ $controller->upload($request);
+ }
+
+}
diff --git a/core/modules/file/tests/file_test/src/Form/FileRequiredTestForm.php b/core/modules/file/tests/file_test/src/Form/FileRequiredTestForm.php
index 1491510fd648..4bdcf5455f9d 100644
--- a/core/modules/file/tests/file_test/src/Form/FileRequiredTestForm.php
+++ b/core/modules/file/tests/file_test/src/Form/FileRequiredTestForm.php
@@ -14,14 +14,14 @@ class FileRequiredTestForm extends FileTestForm {
/**
* {@inheritdoc}
*/
- public function getFormId() {
+ public function getFormId(): string {
return '_file_required_test_form';
}
/**
* {@inheritdoc}
*/
- public function buildForm(array $form, FormStateInterface $form_state) {
+ public function buildForm(array $form, FormStateInterface $form_state): array {
$form = parent::buildForm($form, $form_state);
$form['file_test_upload']['#required'] = TRUE;
return $form;
diff --git a/core/modules/file/tests/file_test/src/Form/FileTestForm.php b/core/modules/file/tests/file_test/src/Form/FileTestForm.php
index d021a538f44c..902c675cdcf4 100644
--- a/core/modules/file/tests/file_test/src/Form/FileTestForm.php
+++ b/core/modules/file/tests/file_test/src/Form/FileTestForm.php
@@ -6,28 +6,28 @@ namespace Drupal\file_test\Form;
use Drupal\Core\File\FileExists;
use Drupal\Core\File\FileSystemInterface;
-use Drupal\Core\Form\FormInterface;
+use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
* File test form class.
*/
-class FileTestForm implements FormInterface {
+class FileTestForm extends FormBase {
use FileTestFormTrait;
use StringTranslationTrait;
/**
* {@inheritdoc}
*/
- public function getFormId() {
+ public function getFormId(): string {
return '_file_test_form';
}
/**
* {@inheritdoc}
*/
- public function buildForm(array $form, FormStateInterface $form_state) {
+ public function buildForm(array $form, FormStateInterface $form_state): array {
$form = $this->baseForm($form, $form_state);
@@ -42,12 +42,7 @@ class FileTestForm implements FormInterface {
/**
* {@inheritdoc}
*/
- public function validateForm(array &$form, FormStateInterface $form_state) {}
-
- /**
- * {@inheritdoc}
- */
- public function submitForm(array &$form, FormStateInterface $form_state) {
+ public function submitForm(array &$form, FormStateInterface $form_state): void {
// Process the upload and perform validation. Note: we're using the
// form value for the $replace parameter.
if (!$form_state->isValueEmpty('file_subdir')) {
diff --git a/core/modules/jsonapi/jsonapi.api.php b/core/modules/jsonapi/jsonapi.api.php
index 5b2f2002d25c..ca8c5ae993fb 100644
--- a/core/modules/jsonapi/jsonapi.api.php
+++ b/core/modules/jsonapi/jsonapi.api.php
@@ -9,6 +9,7 @@ use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Access\AccessResult;
+use Drupal\jsonapi\JsonApiFilter;
/**
* @defgroup jsonapi_architecture JSON:API Architecture
@@ -263,10 +264,10 @@ use Drupal\Core\Access\AccessResult;
* viewable.
* - AccessResult::neutral() if the implementation has no opinion.
* The supported subsets for which an access result may be returned are:
- * - JSONAPI_FILTER_AMONG_ALL: all entities of the given type.
- * - JSONAPI_FILTER_AMONG_PUBLISHED: all published entities of the given type.
- * - JSONAPI_FILTER_AMONG_ENABLED: all enabled entities of the given type.
- * - JSONAPI_FILTER_AMONG_OWN: all entities of the given type owned by the
+ * - JsonApiFilter::AMONG_ALL: all entities of the given type.
+ * - JsonApiFilter::AMONG_PUBLISHED: all published entities of the given type.
+ * - JsonApiFilter::AMONG_ENABLED: all enabled entities of the given type.
+ * - JsonApiFilter::AMONG_OWN: all entities of the given type owned by the
* user for whom access is being checked.
* See the documentation of the above constants for more information about
* each subset.
@@ -278,7 +279,7 @@ function hook_jsonapi_entity_filter_access(EntityTypeInterface $entity_type, Acc
// by all entities of that type to users with that permission.
if ($admin_permission = $entity_type->getAdminPermission()) {
return ([
- JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, $admin_permission),
+ JsonApiFilter::AMONG_ALL => AccessResult::allowedIfHasPermission($account, $admin_permission),
]);
}
}
@@ -305,9 +306,9 @@ function hook_jsonapi_entity_filter_access(EntityTypeInterface $entity_type, Acc
*/
function hook_jsonapi_ENTITY_TYPE_filter_access(EntityTypeInterface $entity_type, AccountInterface $account): array {
return ([
- JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'administer llamas'),
- JSONAPI_FILTER_AMONG_PUBLISHED => AccessResult::allowedIfHasPermission($account, 'view all published llamas'),
- JSONAPI_FILTER_AMONG_OWN => AccessResult::allowedIfHasPermissions($account, ['view own published llamas', 'view own unpublished llamas'], 'AND'),
+ JsonApiFilter::AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'administer llamas'),
+ JsonApiFilter::AMONG_PUBLISHED => AccessResult::allowedIfHasPermission($account, 'view all published llamas'),
+ JsonApiFilter::AMONG_OWN => AccessResult::allowedIfHasPermissions($account, ['view own published llamas', 'view own unpublished llamas'], 'AND'),
]);
}
diff --git a/core/modules/jsonapi/jsonapi.module b/core/modules/jsonapi/jsonapi.module
index 69414af650b9..c512575305a4 100644
--- a/core/modules/jsonapi/jsonapi.module
+++ b/core/modules/jsonapi/jsonapi.module
@@ -11,6 +11,10 @@
* regardless of whether they are published or enabled, and regardless of
* their owner.
*
+ * @deprecated in drupal:11.3.0 and is removed from drupal:13.0.0. Use
+ * \Drupal\jsonapi\JsonApiFilter::AMONG_ALL instead.
+ *
+ * @see https://www.drupal.org/node/3495601
* @see hook_jsonapi_entity_filter_access()
* @see hook_jsonapi_ENTITY_TYPE_filter_access()
*/
@@ -25,6 +29,10 @@ const JSONAPI_FILTER_AMONG_ALL = 'filter_among_all';
* This is used when an entity type has a "published" entity key and there's a
* query condition for the value of that equaling 1.
*
+ * @deprecated in drupal:11.3.0 and is removed from drupal:13.0.0. Use
+ * \Drupal\jsonapi\JsonApiFilter::AMONG_PUBLISHED instead.
+ *
+ * @see https://www.drupal.org/node/3495601
* @see hook_jsonapi_entity_filter_access()
* @see hook_jsonapi_ENTITY_TYPE_filter_access()
*/
@@ -42,6 +50,10 @@ const JSONAPI_FILTER_AMONG_PUBLISHED = 'filter_among_published';
* For the User entity type, which does not have a "status" entity key, the
* "status" field is used.
*
+ * @deprecated in drupal:11.3.0 and is removed from drupal:13.0.0. Use
+ * \Drupal\jsonapi\JsonApiFilter::AMONG_ENABLED instead.
+ *
+ * @see https://www.drupal.org/node/3495601
* @see hook_jsonapi_entity_filter_access()
* @see hook_jsonapi_ENTITY_TYPE_filter_access()
*/
@@ -64,6 +76,10 @@ const JSONAPI_FILTER_AMONG_ENABLED = 'filter_among_enabled';
* - The entity type has an "owner" entity key.
* - There's a filter/query condition for the value equal to the user's ID.
*
+ * @deprecated in drupal:11.3.0 and is removed from drupal:13.0.0. Use
+ * \Drupal\jsonapi\JsonApiFilter::AMONG_OWN instead.
+ *
+ * @see https://www.drupal.org/node/3495601
* @see hook_jsonapi_entity_filter_access()
* @see hook_jsonapi_ENTITY_TYPE_filter_access()
*/
diff --git a/core/modules/jsonapi/src/Access/TemporaryQueryGuard.php b/core/modules/jsonapi/src/Access/TemporaryQueryGuard.php
index d59dca4ec03a..2888fbbec77c 100644
--- a/core/modules/jsonapi/src/Access/TemporaryQueryGuard.php
+++ b/core/modules/jsonapi/src/Access/TemporaryQueryGuard.php
@@ -12,6 +12,7 @@ use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\TypedData\DataReferenceDefinitionInterface;
+use Drupal\jsonapi\JsonApiFilter;
use Drupal\jsonapi\Query\EntityCondition;
use Drupal\jsonapi\Query\EntityConditionGroup;
use Drupal\jsonapi\Query\Filter;
@@ -323,12 +324,12 @@ class TemporaryQueryGuard {
}
/**
- * Gets an access condition for the allowed JSONAPI_FILTER_AMONG_* subsets.
+ * Gets an access condition for the allowed JsonApiFilter::AMONG_* subsets.
*
- * If access is allowed for the JSONAPI_FILTER_AMONG_ALL subset, then no
+ * If access is allowed for the JsonApiFilter::AMONG_ALL subset, then no
* conditions are returned. Otherwise, if access is allowed for
- * JSONAPI_FILTER_AMONG_PUBLISHED, JSONAPI_FILTER_AMONG_ENABLED, or
- * JSONAPI_FILTER_AMONG_OWN, then a condition group is returned for the union
+ * JsonApiFilter::AMONG_PUBLISHED, JsonApiFilter::AMONG_ENABLED, or
+ * JsonApiFilter::AMONG_OWN, then a condition group is returned for the union
* of allowed subsets. If no subsets are allowed, then static::alwaysFalse()
* is returned.
*
@@ -344,12 +345,12 @@ class TemporaryQueryGuard {
* secure an entity query.
*/
protected static function getAccessConditionForKnownSubsets(EntityTypeInterface $entity_type, AccountInterface $account, CacheableMetadata $cacheability) {
- // Get the combined access results for each JSONAPI_FILTER_AMONG_* subset.
+ // Get the combined access results for each JsonApiFilter::AMONG_* subset.
$access_results = static::getAccessResultsFromEntityFilterHook($entity_type, $account);
// No conditions are needed if access is allowed for all entities.
- $cacheability->addCacheableDependency($access_results[JSONAPI_FILTER_AMONG_ALL]);
- if ($access_results[JSONAPI_FILTER_AMONG_ALL]->isAllowed()) {
+ $cacheability->addCacheableDependency($access_results[JsonApiFilter::AMONG_ALL]);
+ if ($access_results[JsonApiFilter::AMONG_ALL]->isAllowed()) {
return NULL;
}
@@ -363,7 +364,7 @@ class TemporaryQueryGuard {
// The "published" subset.
$published_field_name = $entity_type->getKey('published');
if ($published_field_name) {
- $access_result = $access_results[JSONAPI_FILTER_AMONG_PUBLISHED];
+ $access_result = $access_results[JsonApiFilter::AMONG_PUBLISHED];
$cacheability->addCacheableDependency($access_result);
if ($access_result->isAllowed()) {
$conditions[] = new EntityCondition($published_field_name, 1);
@@ -375,7 +376,7 @@ class TemporaryQueryGuard {
// @todo Remove ternary when the 'status' key is added to the User entity type.
$status_field_name = $entity_type->id() === 'user' ? 'status' : $entity_type->getKey('status');
if ($status_field_name) {
- $access_result = $access_results[JSONAPI_FILTER_AMONG_ENABLED];
+ $access_result = $access_results[JsonApiFilter::AMONG_ENABLED];
$cacheability->addCacheableDependency($access_result);
if ($access_result->isAllowed()) {
$conditions[] = new EntityCondition($status_field_name, 1);
@@ -387,7 +388,7 @@ class TemporaryQueryGuard {
// @todo Remove ternary when the 'uid' key is added to the User entity type.
$owner_field_name = $entity_type->id() === 'user' ? 'uid' : $entity_type->getKey('owner');
if ($owner_field_name) {
- $access_result = $access_results[JSONAPI_FILTER_AMONG_OWN];
+ $access_result = $access_results[JsonApiFilter::AMONG_OWN];
$cacheability->addCacheableDependency($access_result);
if ($access_result->isAllowed()) {
$cacheability->addCacheContexts(['user']);
@@ -415,7 +416,7 @@ class TemporaryQueryGuard {
}
/**
- * Gets the combined access result for each JSONAPI_FILTER_AMONG_* subset.
+ * Gets the combined access result for each JsonApiFilter::AMONG_* subset.
*
* This invokes hook_jsonapi_entity_filter_access() and
* hook_jsonapi_ENTITY_TYPE_filter_access() and combines the results from all
@@ -433,10 +434,10 @@ class TemporaryQueryGuard {
protected static function getAccessResultsFromEntityFilterHook(EntityTypeInterface $entity_type, AccountInterface $account) {
/** @var \Drupal\Core\Access\AccessResultInterface[] $combined_access_results */
$combined_access_results = [
- JSONAPI_FILTER_AMONG_ALL => AccessResult::neutral(),
- JSONAPI_FILTER_AMONG_PUBLISHED => AccessResult::neutral(),
- JSONAPI_FILTER_AMONG_ENABLED => AccessResult::neutral(),
- JSONAPI_FILTER_AMONG_OWN => AccessResult::neutral(),
+ JsonApiFilter::AMONG_ALL => AccessResult::neutral(),
+ JsonApiFilter::AMONG_PUBLISHED => AccessResult::neutral(),
+ JsonApiFilter::AMONG_ENABLED => AccessResult::neutral(),
+ JsonApiFilter::AMONG_OWN => AccessResult::neutral(),
];
// Invoke hook_jsonapi_entity_filter_access() and
diff --git a/core/modules/jsonapi/src/Hook/JsonapiHooks.php b/core/modules/jsonapi/src/Hook/JsonapiHooks.php
index 7db5b77b0e80..d5f6d2540fcd 100644
--- a/core/modules/jsonapi/src/Hook/JsonapiHooks.php
+++ b/core/modules/jsonapi/src/Hook/JsonapiHooks.php
@@ -7,6 +7,7 @@ use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\jsonapi\JsonApiFilter;
use Drupal\jsonapi\Routing\Routes;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Hook\Attribute\Hook;
@@ -107,7 +108,7 @@ class JsonapiHooks {
// AccessResult::forbidden() from its implementation of this hook.
if ($admin_permission = $entity_type->getAdminPermission()) {
return [
- JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, $admin_permission),
+ JsonApiFilter::AMONG_ALL => AccessResult::allowedIfHasPermission($account, $admin_permission),
];
}
return [];
@@ -122,8 +123,8 @@ class JsonapiHooks {
// \Drupal\jsonapi\Access\TemporaryQueryGuard adds the condition for
// (isReusable()), so this does not have to.
return [
- JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'access block library'),
- JSONAPI_FILTER_AMONG_PUBLISHED => AccessResult::allowed(),
+ JsonApiFilter::AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'access block library'),
+ JsonApiFilter::AMONG_PUBLISHED => AccessResult::allowed(),
];
}
@@ -136,8 +137,8 @@ class JsonapiHooks {
// \Drupal\jsonapi\Access\TemporaryQueryGuard adds the condition for
// (access to the commented entity), so this does not have to.
return [
- JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'administer comments'),
- JSONAPI_FILTER_AMONG_PUBLISHED => AccessResult::allowedIfHasPermission($account, 'access comments'),
+ JsonApiFilter::AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'administer comments'),
+ JsonApiFilter::AMONG_PUBLISHED => AccessResult::allowedIfHasPermission($account, 'access comments'),
];
}
@@ -148,7 +149,7 @@ class JsonapiHooks {
public function jsonapiEntityTestFilterAccess(EntityTypeInterface $entity_type, AccountInterface $account): array {
// @see \Drupal\entity_test\EntityTestAccessControlHandler::checkAccess()
return [
- JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'view test entity'),
+ JsonApiFilter::AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'view test entity'),
];
}
@@ -161,7 +162,7 @@ class JsonapiHooks {
// \Drupal\jsonapi\Access\TemporaryQueryGuard adds the condition for
// (public OR owner), so this does not have to.
return [
- JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'access content'),
+ JsonApiFilter::AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'access content'),
];
}
@@ -172,7 +173,7 @@ class JsonapiHooks {
public function jsonapiMediaFilterAccess(EntityTypeInterface $entity_type, AccountInterface $account): array {
// @see \Drupal\media\MediaAccessControlHandler::checkAccess()
return [
- JSONAPI_FILTER_AMONG_PUBLISHED => AccessResult::allowedIfHasPermission($account, 'view media'),
+ JsonApiFilter::AMONG_PUBLISHED => AccessResult::allowedIfHasPermission($account, 'view media'),
];
}
@@ -184,30 +185,30 @@ class JsonapiHooks {
// @see \Drupal\node\NodeAccessControlHandler::access()
if ($account->hasPermission('bypass node access')) {
return [
- JSONAPI_FILTER_AMONG_ALL => AccessResult::allowed()->cachePerPermissions(),
+ JsonApiFilter::AMONG_ALL => AccessResult::allowed()->cachePerPermissions(),
];
}
if (!$account->hasPermission('access content')) {
$forbidden = AccessResult::forbidden("The 'access content' permission is required.")->cachePerPermissions();
return [
- JSONAPI_FILTER_AMONG_ALL => $forbidden,
- JSONAPI_FILTER_AMONG_OWN => $forbidden,
- JSONAPI_FILTER_AMONG_PUBLISHED => $forbidden,
+ JsonApiFilter::AMONG_ALL => $forbidden,
+ JsonApiFilter::AMONG_OWN => $forbidden,
+ JsonApiFilter::AMONG_PUBLISHED => $forbidden,
// For legacy reasons, the Node entity type has a "status" key, so
// forbid this subset as well, even though it has no semantic meaning.
- JSONAPI_FILTER_AMONG_ENABLED => $forbidden,
+ JsonApiFilter::AMONG_ENABLED => $forbidden,
];
}
return [
- // @see \Drupal\node\NodeAccessControlHandler::checkAccess()
- JSONAPI_FILTER_AMONG_OWN => AccessResult::allowedIfHasPermission($account, 'view own unpublished content'),
- // @see \Drupal\node\NodeGrantDatabaseStorage::access()
- // Note that:
- // - This is just for the default grant. Other node access conditions
- // are added via the 'node_access' query tag.
- // - Permissions were checked earlier in this function, so we must
- // vary the cache by them.
- JSONAPI_FILTER_AMONG_PUBLISHED => AccessResult::allowed()->cachePerPermissions(),
+ // @see \Drupal\node\NodeAccessControlHandler::checkAccess()
+ JsonApiFilter::AMONG_OWN => AccessResult::allowedIfHasPermission($account, 'view own unpublished content'),
+ // @see \Drupal\node\NodeGrantDatabaseStorage::access()
+ // Note that:
+ // - This is just for the default grant. Other node access conditions
+ // are added via the 'node_access' query tag.
+ // - Permissions were checked earlier in this function, so we must
+ // vary the cache by them.
+ JsonApiFilter::AMONG_PUBLISHED => AccessResult::allowed()->cachePerPermissions(),
];
}
@@ -221,7 +222,7 @@ class JsonapiHooks {
// "shortcut_set = $shortcut_set_storage->getDisplayedToUser($current_user)"
// so this does not have to.
return [
- JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'administer shortcuts')->orIf(AccessResult::allowedIfHasPermissions($account, [
+ JsonApiFilter::AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'administer shortcuts')->orIf(AccessResult::allowedIfHasPermissions($account, [
'access shortcuts',
'customize shortcut links',
])),
@@ -235,8 +236,8 @@ class JsonapiHooks {
public function jsonapiTaxonomyTermFilterAccess(EntityTypeInterface $entity_type, AccountInterface $account): array {
// @see \Drupal\taxonomy\TermAccessControlHandler::checkAccess()
return [
- JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'administer taxonomy'),
- JSONAPI_FILTER_AMONG_PUBLISHED => AccessResult::allowedIfHasPermission($account, 'access content'),
+ JsonApiFilter::AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'administer taxonomy'),
+ JsonApiFilter::AMONG_PUBLISHED => AccessResult::allowedIfHasPermission($account, 'access content'),
];
}
@@ -249,8 +250,8 @@ class JsonapiHooks {
// \Drupal\jsonapi\Access\TemporaryQueryGuard adds the condition for
// (!isAnonymous()), so this does not have to.
return [
- JSONAPI_FILTER_AMONG_OWN => AccessResult::allowed(),
- JSONAPI_FILTER_AMONG_ENABLED => AccessResult::allowedIfHasPermission($account, 'access user profiles'),
+ JsonApiFilter::AMONG_OWN => AccessResult::allowed(),
+ JsonApiFilter::AMONG_ENABLED => AccessResult::allowedIfHasPermission($account, 'access user profiles'),
];
}
@@ -261,8 +262,8 @@ class JsonapiHooks {
public function jsonapiWorkspaceFilterAccess(EntityTypeInterface $entity_type, AccountInterface $account): array {
// @see \Drupal\workspaces\WorkspaceAccessControlHandler::checkAccess()
return [
- JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'view any workspace'),
- JSONAPI_FILTER_AMONG_OWN => AccessResult::allowedIfHasPermission($account, 'view own workspace'),
+ JsonApiFilter::AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'view any workspace'),
+ JsonApiFilter::AMONG_OWN => AccessResult::allowedIfHasPermission($account, 'view own workspace'),
];
}
diff --git a/core/modules/jsonapi/src/JsonApiFilter.php b/core/modules/jsonapi/src/JsonApiFilter.php
new file mode 100644
index 000000000000..c9ac90be7af7
--- /dev/null
+++ b/core/modules/jsonapi/src/JsonApiFilter.php
@@ -0,0 +1,77 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\jsonapi;
+
+/**
+ * JsonApi filter options.
+ */
+final class JsonApiFilter {
+
+ /**
+ * Array key for denoting type-based filtering access.
+ *
+ * Array key for denoting access to filter among all entities of a given type,
+ * regardless of whether they are published or enabled, and regardless of
+ * their owner.
+ *
+ * @see hook_jsonapi_entity_filter_access()
+ * @see hook_jsonapi_ENTITY_TYPE_filter_access()
+ */
+ const AMONG_ALL = 'filter_among_all';
+
+ /**
+ * Array key for denoting type-based published-only filtering access.
+ *
+ * Array key for denoting access to filter among all published entities of a
+ * given type, regardless of their owner.
+ *
+ * This is used when an entity type has a "published" entity key and there's a
+ * query condition for the value of that equaling 1.
+ *
+ * @see hook_jsonapi_entity_filter_access()
+ * @see hook_jsonapi_ENTITY_TYPE_filter_access()
+ */
+ const AMONG_PUBLISHED = 'filter_among_published';
+
+ /**
+ * Array key for denoting type-based enabled-only filtering access.
+ *
+ * Array key for denoting access to filter among all enabled entities of a
+ * given type, regardless of their owner.
+ *
+ * This is used when an entity type has a "status" entity key and there's a
+ * query condition for the value of that equaling 1.
+ *
+ * For the User entity type, which does not have a "status" entity key, the
+ * "status" field is used.
+ *
+ * @see hook_jsonapi_entity_filter_access()
+ * @see hook_jsonapi_ENTITY_TYPE_filter_access()
+ */
+ const AMONG_ENABLED = 'filter_among_enabled';
+
+ /**
+ * Array key for denoting type-based owned-only filtering access.
+ *
+ * Array key for denoting access to filter among all entities of a given type,
+ * regardless of whether they are published or enabled, so long as they are
+ * owned by the user for whom access is being checked.
+ *
+ * When filtering among User entities, this is used when access is being
+ * checked for an authenticated user and there's a query condition
+ * limiting the result set to just that user's entity object.
+ *
+ * When filtering among entities of another type, this is used when all of the
+ * following conditions are met:
+ * - Access is being checked for an authenticated user.
+ * - The entity type has an "owner" entity key.
+ * - There's a filter/query condition for the value equal to the user's ID.
+ *
+ * @see hook_jsonapi_entity_filter_access()
+ * @see hook_jsonapi_ENTITY_TYPE_filter_access()
+ */
+ const AMONG_OWN = 'filter_among_own';
+
+}
diff --git a/core/modules/layout_builder/src/InlineBlockEntityOperations.php b/core/modules/layout_builder/src/InlineBlockEntityOperations.php
index 16bda99a45cd..7a832ad284c6 100644
--- a/core/modules/layout_builder/src/InlineBlockEntityOperations.php
+++ b/core/modules/layout_builder/src/InlineBlockEntityOperations.php
@@ -134,8 +134,6 @@ class InlineBlockEntityOperations implements ContainerInjectionInterface {
* The parent entity.
*/
public function handleEntityDelete(EntityInterface $entity) {
- // @todo In https://www.drupal.org/node/3008943 call
- // \Drupal\layout_builder\LayoutEntityHelperTrait::isLayoutCompatibleEntity().
$this->usage->removeByLayoutEntity($entity);
}
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index f14d843faa10..1fb4071ba47d 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -36,8 +36,12 @@ use Drupal\node\NodeTypeInterface;
* @return array|false
* A renderable array containing a list of linked node titles fetched from
* $result, or FALSE if there are no rows in $result.
+ *
+ * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. There is no replacement.
+ * @see https://www.drupal.org/node/3531959
*/
function node_title_list(StatementInterface $result, $title = NULL) {
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. There is no replacement. See https://www.drupal.org/node/3531959', E_USER_DEPRECATED);
$items = [];
$num_rows = FALSE;
$nids = [];
diff --git a/core/modules/system/tests/fixtures/HtaccessTest/access_test.css b/core/modules/system/tests/fixtures/HtaccessTest/access_test.css
new file mode 100644
index 000000000000..55cf1bbe6351
--- /dev/null
+++ b/core/modules/system/tests/fixtures/HtaccessTest/access_test.css
@@ -0,0 +1,6 @@
+/*
+ * DO NOT EDIT THIS FILE.
+ * See the following change record for more information,
+ * https://www.drupal.org/node/3084859
+ * @preserve
+ */ /* The test CSS file must not be empty. */
diff --git a/core/modules/system/tests/fixtures/HtaccessTest/access_test.less b/core/modules/system/tests/fixtures/HtaccessTest/access_test.less
new file mode 100644
index 000000000000..e69de29bb2d1
--- /dev/null
+++ b/core/modules/system/tests/fixtures/HtaccessTest/access_test.less
diff --git a/core/modules/system/tests/fixtures/HtaccessTest/access_test.pcss b/core/modules/system/tests/fixtures/HtaccessTest/access_test.pcss
new file mode 100644
index 000000000000..e69de29bb2d1
--- /dev/null
+++ b/core/modules/system/tests/fixtures/HtaccessTest/access_test.pcss
diff --git a/core/modules/system/tests/fixtures/HtaccessTest/access_test.pcss.css b/core/modules/system/tests/fixtures/HtaccessTest/access_test.pcss.css
new file mode 100644
index 000000000000..607f53ef4ef3
--- /dev/null
+++ b/core/modules/system/tests/fixtures/HtaccessTest/access_test.pcss.css
@@ -0,0 +1 @@
+/* The test CSS file must not be empty. */
diff --git a/core/modules/system/tests/fixtures/HtaccessTest/access_test.sass b/core/modules/system/tests/fixtures/HtaccessTest/access_test.sass
new file mode 100644
index 000000000000..e69de29bb2d1
--- /dev/null
+++ b/core/modules/system/tests/fixtures/HtaccessTest/access_test.sass
diff --git a/core/modules/system/tests/fixtures/HtaccessTest/access_test.scss b/core/modules/system/tests/fixtures/HtaccessTest/access_test.scss
new file mode 100644
index 000000000000..e69de29bb2d1
--- /dev/null
+++ b/core/modules/system/tests/fixtures/HtaccessTest/access_test.scss
diff --git a/core/modules/system/tests/modules/container_initialize/container_initialize.info.yml b/core/modules/system/tests/modules/container_initialize/container_initialize.info.yml
new file mode 100644
index 000000000000..46411d2ea544
--- /dev/null
+++ b/core/modules/system/tests/modules/container_initialize/container_initialize.info.yml
@@ -0,0 +1,5 @@
+name: 'Container initialize'
+type: module
+description: 'Support module for HookCollectorPass testing.'
+package: Testing
+version: VERSION
diff --git a/core/modules/system/tests/modules/container_initialize/container_initialize.module b/core/modules/system/tests/modules/container_initialize/container_initialize.module
new file mode 100644
index 000000000000..5c8e0aff74ea
--- /dev/null
+++ b/core/modules/system/tests/modules/container_initialize/container_initialize.module
@@ -0,0 +1,10 @@
+<?php
+
+/**
+ * @file
+ * Used to test bare container calls in .module files.
+ */
+
+declare(strict_types=1);
+
+\Drupal::getContainer()->getParameter('site.path');
diff --git a/core/modules/system/tests/src/Functional/Hook/HookCollectorPassTest.php b/core/modules/system/tests/src/Functional/Hook/HookCollectorPassTest.php
new file mode 100644
index 000000000000..59d69d1242cb
--- /dev/null
+++ b/core/modules/system/tests/src/Functional/Hook/HookCollectorPassTest.php
@@ -0,0 +1,61 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\system\Functional\Hook;
+
+use Drupal\Tests\BrowserTestBase;
+use Drupal\Core\Url;
+
+/**
+ * Tests services in .module files.
+ *
+ * @group Hook
+ */
+class HookCollectorPassTest extends BrowserTestBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ protected static $modules = ['container_initialize'];
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $defaultTheme = 'stark';
+
+ /**
+ * Tests installing a module with a Drupal container call outside functions.
+ *
+ * If this is removed then it needs to be moved to a test that installs modules through
+ * admin/modules.
+ */
+ public function testContainerOutsideFunction(): void {
+ $settings['settings']['rebuild_access'] = (object) [
+ 'value' => TRUE,
+ 'required' => TRUE,
+ ];
+
+ // This simulates installing the module and running a cache rebuild in a
+ // separate request.
+ $this->writeSettings($settings);
+ $this->rebuildAll();
+ $this->drupalGet(Url::fromUri('base:core/rebuild.php'));
+ $this->assertSession()->pageTextNotContains('ContainerNotInitializedException');
+ // Successful response from rebuild.php should redirect to the front page.
+ $this->assertSession()->addressEquals('/');
+
+ // If this file is removed then this test needs to be updated to trigger
+ // the container rebuild error from https://www.drupal.org/i/3505049
+ $config_module_file = $this->root . '/core/modules/system/tests/modules/container_initialize/container_initialize.module';
+ $this->assertFileExists($config_module_file, 'This test depends on a container call in a .module file');
+ // Confirm that the file still has a bare container call.
+ $bare_container = "declare(strict_types=1);
+
+\Drupal::getContainer()->getParameter('site.path');
+";
+ $file_content = file_get_contents($config_module_file);
+ $this->assertStringContainsString($bare_container, $file_content, 'container_initialize.module container test feature is missing.');
+ }
+
+}
diff --git a/core/modules/system/tests/src/Functional/System/HtaccessTest.php b/core/modules/system/tests/src/Functional/System/HtaccessTest.php
index 000e084296ce..9952f69cc7bc 100644
--- a/core/modules/system/tests/src/Functional/System/HtaccessTest.php
+++ b/core/modules/system/tests/src/Functional/System/HtaccessTest.php
@@ -39,6 +39,7 @@ class HtaccessTest extends BrowserTestBase {
'engine',
'inc',
'install',
+ 'less',
'make',
'module',
'module~',
@@ -47,6 +48,8 @@ class HtaccessTest extends BrowserTestBase {
'module.save',
'module.swo',
'module.swp',
+ 'pcss',
+ 'pcss.css',
'php~',
'php.bak',
'php.orig',
@@ -55,6 +58,8 @@ class HtaccessTest extends BrowserTestBase {
'php.swp',
'profile',
'po',
+ 'sass',
+ 'scss',
'sh',
'sql',
'theme',
diff --git a/core/tests/Drupal/Tests/Composer/Plugin/Unpack/Functional/UnpackRecipeTest.php b/core/tests/Drupal/BuildTests/Composer/Plugin/Unpack/Functional/UnpackRecipeTest.php
index b74349256892..1397a78cf694 100644
--- a/core/tests/Drupal/Tests/Composer/Plugin/Unpack/Functional/UnpackRecipeTest.php
+++ b/core/tests/Drupal/BuildTests/Composer/Plugin/Unpack/Functional/UnpackRecipeTest.php
@@ -2,7 +2,7 @@
declare(strict_types=1);
-namespace Drupal\Tests\Composer\Plugin\Unpack\Functional;
+namespace Drupal\BuildTests\Composer\Plugin\Unpack\Functional;
use Composer\InstalledVersions;
use Composer\Util\Filesystem;