diff options
Diffstat (limited to 'core')
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; |