diff options
Diffstat (limited to 'core/modules')
10 files changed, 132 insertions, 82 deletions
diff --git a/core/modules/block_content/src/Hook/BlockContentHooks.php b/core/modules/block_content/src/Hook/BlockContentHooks.php index bd1cfd608f14..4eef9bb8580c 100644 --- a/core/modules/block_content/src/Hook/BlockContentHooks.php +++ b/core/modules/block_content/src/Hook/BlockContentHooks.php @@ -29,11 +29,11 @@ class BlockContentHooks { $field_ui = \Drupal::moduleHandler()->moduleExists('field_ui') ? Url::fromRoute('help.page', ['name' => 'field_ui'])->toString() : '#'; $output = ''; $output .= '<h2>' . $this->t('About') . '</h2>'; - $output .= '<p>' . $this->t('The Block Content module allows you to create and manage custom <em>block types</em> and <em>content-containing blocks</em>. For more information, see the <a href=":online-help">online documentation for the Block Content module</a>.', [':online-help' => 'https://www.drupal.org/documentation/modules/block_content']) . '</p>'; + $output .= '<p>' . $this->t('The Block Content module manages the creation, editing, and deletion of content blocks. Content blocks are field-able content entities managed by the <a href=":field">Field module</a>. For more information, see the <a href=":block-content">online documentation for the Block Content module</a>.', [':block-content' => 'https://www.drupal.org/documentation/modules/block_content', ':field' => Url::fromRoute('help.page', ['name' => 'field'])->toString()]) . '</p>'; $output .= '<h2>' . $this->t('Uses') . '</h2>'; $output .= '<dl>'; $output .= '<dt>' . $this->t('Creating and managing block types') . '</dt>'; - $output .= '<dd>' . $this->t('Users with the <em>Administer blocks</em> permission can create and edit block types with fields and display settings, from the <a href=":types">Block types</a> page under the Structure menu. For more information about managing fields and display settings, see the <a href=":field-ui">Field UI module help</a> and <a href=":field">Field module help</a>.', [ + $output .= '<dd>' . $this->t('Users with the <em>Administer block types</em> permission can create and edit block types with fields and display settings, from the <a href=":types">Block types</a> page under the Structure menu. For more information about managing fields and display settings, see the <a href=":field-ui">Field UI module help</a> and <a href=":field">Field module help</a>.', [ ':types' => Url::fromRoute('entity.block_content_type.collection')->toString(), ':field-ui' => $field_ui, ':field' => Url::fromRoute('help.page', [ @@ -41,9 +41,9 @@ class BlockContentHooks { ])->toString(), ]) . '</dd>'; $output .= '<dt>' . $this->t('Creating content blocks') . '</dt>'; - $output .= '<dd>' . $this->t('Users with the <em>Administer blocks</em> permission can create, edit, and delete content blocks of each defined block type, from the <a href=":block-library">Content blocks page</a>. After creating a block, place it in a region from the <a href=":blocks">Block layout page</a>, just like blocks provided by other modules.', [ - ':blocks' => Url::fromRoute('block.admin_display')->toString(), - ':block-library' => Url::fromRoute('entity.block_content.collection')->toString(), + $output .= '<dd>' . $this->t('Users with the <em>Administer block content</em> or <em>Create new content block</em> permissions for an individual block type are able to add content blocks. These can be created on the <a href=":add-content-block">Add content block page</a> or on the <em>Place block</em> modal on the <a href=":block-layout">Block Layout page</a> and are reusable across the entire site. Content blocks created in Layout Builder for a content type or individual node layouts are not reusable and also called inline blocks.', [ + ':add-content-block' => Url::fromRoute('block_content.add_page')->toString(), + ':block-layout' => Url::fromRoute('block.admin_display')->toString(), ]) . '</dd>'; $output .= '</dl>'; return $output; diff --git a/core/modules/file/src/Plugin/Field/FieldType/FileItem.php b/core/modules/file/src/Plugin/Field/FieldType/FileItem.php index f09740667e32..2e9be38dacb6 100644 --- a/core/modules/file/src/Plugin/Field/FieldType/FileItem.php +++ b/core/modules/file/src/Plugin/Field/FieldType/FileItem.php @@ -360,8 +360,11 @@ class FileItem extends EntityReferenceItem { $dirname = static::doGetUploadLocation($settings); \Drupal::service('file_system')->prepareDirectory($dirname, FileSystemInterface::CREATE_DIRECTORY); + // Ensure directory ends with a slash. + $dirname .= str_ends_with($dirname, '/') ? '' : '/'; + // Generate a file entity. - $destination = $dirname . '/' . $random->name(10, TRUE) . '.txt'; + $destination = $dirname . $random->name(10) . '.txt'; $data = $random->paragraphs(3); /** @var \Drupal\file\FileRepositoryInterface $file_repository */ $file_repository = \Drupal::service('file.repository'); diff --git a/core/modules/file/tests/src/Kernel/FileItemTest.php b/core/modules/file/tests/src/Kernel/FileItemTest.php index 09a28b68f0f1..c01cf28a1151 100644 --- a/core/modules/file/tests/src/Kernel/FileItemTest.php +++ b/core/modules/file/tests/src/Kernel/FileItemTest.php @@ -6,12 +6,14 @@ namespace Drupal\Tests\file\Kernel; use Drupal\Core\Field\FieldItemInterface; use Drupal\Core\Field\FieldItemListInterface; +use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\entity_test\Entity\EntityTest; use Drupal\field\Entity\FieldConfig; use Drupal\Tests\field\Kernel\FieldKernelTestBase; use Drupal\field\Entity\FieldStorageConfig; use Drupal\file\Entity\File; +use Drupal\file\Plugin\Field\FieldType\FileItem; use Drupal\user\Entity\Role; /** @@ -155,6 +157,48 @@ class FileItemTest extends FieldKernelTestBase { \Drupal::service('renderer')->renderRoot($output); $this->assertTrue(!empty($entity->file_test->entity)); $this->assertEquals($uri, $entity->file_test->entity->getFileUri()); + + // Test file URIs with empty and custom directories. + $this->validateFileUriForDirectory( + '', 'public://' + ); + $this->validateFileUriForDirectory( + 'custom_directory/subdir', 'public://custom_directory/subdir/' + ); + } + + /** + * Tests file URIs generated for a given file directory. + * + * @param string $file_directory + * The file directory to test (e.g., empty or 'custom_directory/subdir'). + * @param string $expected_start + * The expected starting string of the file URI (e.g., 'public://'). + */ + private function validateFileUriForDirectory(string $file_directory, string $expected_start): void { + // Mock the field definition with the specified file directory. + $definition = $this->createMock(FieldDefinitionInterface::class); + $definition->expects($this->any()) + ->method('getSettings') + ->willReturn([ + 'file_extensions' => 'txt', + 'file_directory' => $file_directory, + 'uri_scheme' => 'public', + 'display_default' => TRUE, + ]); + + // Generate a sample file value. + $value = FileItem::generateSampleValue($definition); + $this->assertNotEmpty($value); + + // Load the file entity and get its URI. + $fid = $value['target_id']; + $file = File::load($fid); + $fileUri = $file->getFileUri(); + + // Verify the file URI starts with the expected protocol and structure. + $this->assertStringStartsWith($expected_start, $fileUri); + $this->assertMatchesRegularExpression('#^' . preg_quote($expected_start, '#') . '[^/]+#', $fileUri); } } diff --git a/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php b/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php index 7ed6b0d3371c..72937d4e79a3 100644 --- a/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php +++ b/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php @@ -389,7 +389,9 @@ class ImageItem extends FileItem { $image->setFileName($file_system->basename($path)); $destination_dir = static::doGetUploadLocation($settings); $file_system->prepareDirectory($destination_dir, FileSystemInterface::CREATE_DIRECTORY); - $destination = $destination_dir . '/' . basename($path); + // Ensure directory ends with a slash. + $destination_dir .= str_ends_with($destination_dir, '/') ? '' : '/'; + $destination = $destination_dir . basename($path); $file = \Drupal::service('file.repository')->move($image, $destination); $images[$extension][$min_resolution][$max_resolution][$file->id()] = $file; } diff --git a/core/modules/image/tests/src/Kernel/ImageItemTest.php b/core/modules/image/tests/src/Kernel/ImageItemTest.php index 0598b25e9db8..55f2503686d4 100644 --- a/core/modules/image/tests/src/Kernel/ImageItemTest.php +++ b/core/modules/image/tests/src/Kernel/ImageItemTest.php @@ -9,6 +9,7 @@ use Drupal\Core\Entity\ContentEntityForm; use Drupal\Core\Entity\Entity\EntityFormDisplay; use Drupal\Core\Entity\EntityDisplayRepositoryInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\FieldStorageDefinitionInterface; @@ -19,6 +20,7 @@ use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; use Drupal\file\Entity\File; use Drupal\Tests\field\Kernel\FieldKernelTestBase; +use Drupal\image\Plugin\Field\FieldType\ImageItem; use Drupal\user\Entity\Role; /** @@ -208,6 +210,14 @@ class ImageItemTest extends FieldKernelTestBase { } /** + * Tests image URIs for empty and custom directories. + */ + public function testImageUriDirectories(): void { + $this->validateImageUriForDirectory('', 'public://'); + $this->validateImageUriForDirectory('custom_directory/subdir', 'public://custom_directory/subdir/'); + } + + /** * Tests display_default. */ public function testDisplayDefaultValue(): void { @@ -225,4 +235,36 @@ class ImageItemTest extends FieldKernelTestBase { self::assertEquals(1, $form_state->getValue(['image_test', 0, 'display'])); } + /** + * Validates the image file URI generated for a given file directory. + * + * @param string $file_directory + * The file directory to test (e.g., empty or 'custom_directory/subdir'). + * @param string $expected_start + * The expected starting string of the file URI (e.g., 'public://'). + */ + private function validateImageUriForDirectory(string $file_directory, string $expected_start): void { + // Mock the field definition with the specified file directory. + $definition = $this->createMock(FieldDefinitionInterface::class); + $definition->expects($this->any()) + ->method('getSettings') + ->willReturn([ + 'file_extensions' => 'jpg', + 'file_directory' => $file_directory, + 'uri_scheme' => 'public', + ]); + // Generate sample value and check the URI format. + $value = ImageItem::generateSampleValue($definition); + $this->assertNotEmpty($value); + + // Load the file entity and get its URI. + $fid = $value['target_id']; + $file = File::load($fid); + $fileUri = $file->getFileUri(); + + // Verify the file URI starts with the expected protocol and structure. + $this->assertStringStartsWith($expected_start, $fileUri); + $this->assertMatchesRegularExpression('#^' . preg_quote($expected_start, '#') . '[^/]+#', $fileUri); + } + } diff --git a/core/modules/locale/locale.install b/core/modules/locale/locale.install index 457d37890fa0..d3377f3773eb 100644 --- a/core/modules/locale/locale.install +++ b/core/modules/locale/locale.install @@ -21,12 +21,6 @@ function locale_install(): void { \Drupal::configFactory()->getEditable('locale.settings')->set('translation.path', $directory)->save(); } \Drupal::service('file_system')->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS); - - $t_args = [ - ':translate_status' => base_path() . 'admin/reports/translations/check?destination=' . urlencode(base_path() . 'admin/reports/translations'), - ]; - $message = t('Check <a href=":translate_status">available translations</a> for your language(s).', $t_args); - \Drupal::messenger()->addStatus($message); } /** diff --git a/core/modules/locale/src/LocaleTranslation.php b/core/modules/locale/src/LocaleTranslation.php index a9ccc93626f0..99e852650ee6 100644 --- a/core/modules/locale/src/LocaleTranslation.php +++ b/core/modules/locale/src/LocaleTranslation.php @@ -20,7 +20,9 @@ use Symfony\Component\HttpFoundation\RequestStack; */ class LocaleTranslation implements TranslatorInterface, DestructableInterface { - use DependencySerializationTrait; + use DependencySerializationTrait { + __sleep as traitSleep; + } /** * Storage for strings. @@ -161,4 +163,13 @@ class LocaleTranslation implements TranslatorInterface, DestructableInterface { } } + /** + * {@inheritdoc} + */ + public function __sleep(): array { + // ::$translations is an array of LocaleLookup objects, which have the + // database service injected and therefore cannot be serialized safely. + return array_diff($this->traitSleep(), ['translations']); + } + } diff --git a/core/modules/locale/tests/src/Functional/LocaleInstallTest.php b/core/modules/locale/tests/src/Functional/LocaleInstallTest.php deleted file mode 100644 index fc81fdb19b52..000000000000 --- a/core/modules/locale/tests/src/Functional/LocaleInstallTest.php +++ /dev/null @@ -1,48 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace Drupal\Tests\locale\Functional; - -use Drupal\Tests\BrowserTestBase; - -/** - * Test installation of Locale module. - * - * @group locale - */ -class LocaleInstallTest extends BrowserTestBase { - - /** - * {@inheritdoc} - */ - protected static $modules = [ - 'system', - 'file', - 'language', - ]; - - /** - * {@inheritdoc} - */ - protected $defaultTheme = 'stark'; - - /** - * Tests Locale install message. - */ - public function testLocaleInstallMessage(): void { - $admin_user = $this->drupalCreateUser([ - 'access administration pages', - 'administer modules', - ]); - $this->drupalLogin($admin_user); - - $edit = []; - $edit['modules[locale][enable]'] = 'locale'; - $this->drupalGet('admin/modules'); - $this->submitForm($edit, 'Install'); - - $this->assertSession()->statusMessageContains('available translations', 'status'); - } - -} diff --git a/core/modules/locale/tests/src/Kernel/LocaleTranslationTest.php b/core/modules/locale/tests/src/Kernel/LocaleTranslationTest.php index 316384330f8b..b52d29348253 100644 --- a/core/modules/locale/tests/src/Kernel/LocaleTranslationTest.php +++ b/core/modules/locale/tests/src/Kernel/LocaleTranslationTest.php @@ -21,11 +21,29 @@ class LocaleTranslationTest extends KernelTestBase { ]; /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + + $this->installSchema('locale', [ + 'locales_location', + 'locales_source', + 'locales_target', + ]); + } + + /** * Tests that \Drupal\locale\LocaleTranslation is serializable. */ public function testSerializable(): void { + /** @var \Drupal\locale\LocaleTranslation $translation */ $translation = $this->container->get('string_translator.locale.lookup'); $this->assertInstanceOf(LocaleTranslation::class, $translation); + // Ensure that the \Drupal\locale\LocaleTranslation::$translations property + // has some cached translations in it. Without this, serialization will not + // actually be tested fully. + $translation->getStringTranslation('es', 'test', ''); // Prove that serialization and deserialization works without errors. $this->assertNotNull($translation); diff --git a/core/modules/system/tests/src/Functional/FileTransfer/FileTransferTest.php b/core/modules/system/tests/src/Functional/FileTransfer/FileTransferTest.php index ae157ab56624..fa6bda652deb 100644 --- a/core/modules/system/tests/src/Functional/FileTransfer/FileTransferTest.php +++ b/core/modules/system/tests/src/Functional/FileTransfer/FileTransferTest.php @@ -88,26 +88,10 @@ class FileTransferTest extends BrowserTestBase { */ public function testJail(): void { $source = $this->_buildFakeModule(); - - // This convoluted piece of code is here because our testing framework does - // not support expecting exceptions. - $got_it = FALSE; - try { - $this->testConnection->copyDirectory($source, sys_get_temp_dir()); - } - catch (FileTransferException) { - $got_it = TRUE; - } - $this->assertTrue($got_it, 'Was not able to copy a directory outside of the jailed area.'); - - $got_it = TRUE; - try { - $this->testConnection->copyDirectory($source, $this->root . '/' . PublicStream::basePath()); - } - catch (FileTransferException) { - $got_it = FALSE; - } - $this->assertTrue($got_it, 'Was able to copy a directory inside of the jailed area'); + $this->testConnection->copyDirectory($source, $this->root . '/' . PublicStream::basePath()); + $this->expectException(FileTransferException::class); + $this->expectExceptionMessage('@directory is outside of the @jail'); + $this->testConnection->copyDirectory($source, sys_get_temp_dir()); } } |