diff --git a/package_manager/src/Validator/WritableFileSystemValidator.php b/package_manager/src/Validator/WritableFileSystemValidator.php index 5b0a507dff28f0c861aed7728a877821fec295db..d5701757c92bc37ab126f1b5691fa1a03a2edfd6 100644 --- a/package_manager/src/Validator/WritableFileSystemValidator.php +++ b/package_manager/src/Validator/WritableFileSystemValidator.php @@ -41,14 +41,23 @@ class WritableFileSystemValidator implements EventSubscriberInterface { public function validate(PreOperationStageEvent $event): void { $messages = []; - $drupal_root = $this->pathLocator->getProjectRoot(); + $project_root = $this->pathLocator->getProjectRoot(); + + // If the web (Drupal) root and project root are different, validate the + // web root separately. $web_root = $this->pathLocator->getWebRoot(); if ($web_root) { - $drupal_root .= DIRECTORY_SEPARATOR . $web_root; + $drupal_root = $project_root . DIRECTORY_SEPARATOR . $web_root; + if (!is_writable($drupal_root)) { + $messages[] = $this->t('The Drupal directory "@dir" is not writable.', [ + '@dir' => $drupal_root, + ]); + } } - if (!is_writable($drupal_root)) { - $messages[] = $this->t('The Drupal directory "@dir" is not writable.', [ - '@dir' => $drupal_root, + + if (!is_writable($project_root)) { + $messages[] = $this->t('The project root directory "@dir" is not writable.', [ + '@dir' => $project_root, ]); } diff --git a/package_manager/tests/src/Kernel/WritableFileSystemValidatorTest.php b/package_manager/tests/src/Kernel/WritableFileSystemValidatorTest.php index ec5f13f4ae367433e420cc5861509e0197da2aae..484218aa937a2e4515b442068bd629950a5bbaba 100644 --- a/package_manager/tests/src/Kernel/WritableFileSystemValidatorTest.php +++ b/package_manager/tests/src/Kernel/WritableFileSystemValidatorTest.php @@ -32,37 +32,80 @@ class WritableFileSystemValidatorTest extends PackageManagerKernelTestBase { */ public function providerWritable(): array { // @see \Drupal\Tests\package_manager\Traits\ValidationTestTrait::resolvePlaceholdersInArrayValuesWithRealPaths() - $root_error = t('The Drupal directory "<PROJECT_ROOT>" is not writable.'); + $drupal_root_error = t('The Drupal directory "<PROJECT_ROOT>/web" is not writable.'); $vendor_error = t('The vendor directory "<VENDOR_DIR>" is not writable.'); + $project_root_error = t('The project root directory "<PROJECT_ROOT>" is not writable.'); $summary = t('The file system is not writable.'); $writable_permission = 0777; $non_writable_permission = 0550; return [ - 'root and vendor are writable' => [ + 'root and vendor are writable, nested web root' => [ $writable_permission, $writable_permission, + $writable_permission, + 'web', [], ], - 'root writable, vendor not writable' => [ + 'root writable, vendor not writable, nested web root' => [ + $writable_permission, $writable_permission, $non_writable_permission, + 'web', [ ValidationResult::createError([$vendor_error], $summary), ], ], - 'root not writable, vendor writable' => [ + 'root not writable, vendor writable, nested web root' => [ + $non_writable_permission, + $non_writable_permission, + $writable_permission, + 'web', + [ + ValidationResult::createError([$drupal_root_error, $project_root_error], $summary), + ], + ], + 'nothing writable, nested web root' => [ + $non_writable_permission, $non_writable_permission, + $non_writable_permission, + 'web', + [ + ValidationResult::createError([$drupal_root_error, $project_root_error, $vendor_error], $summary), + ], + ], + 'root and vendor are writable, non-nested web root' => [ + $writable_permission, + $writable_permission, + $writable_permission, + '', + [], + ], + 'root writable, vendor not writable, non-nested web root' => [ $writable_permission, + $writable_permission, + $non_writable_permission, + '', [ - ValidationResult::createError([$root_error], $summary), + ValidationResult::createError([$vendor_error], $summary), ], ], - 'nothing writable' => [ + 'root not writable, vendor writable, non-nested web root' => [ $non_writable_permission, $non_writable_permission, + $writable_permission, + '', [ - ValidationResult::createError([$root_error, $vendor_error], $summary), + ValidationResult::createError([$project_root_error], $summary), + ], + ], + 'nothing writable, non-nested web root' => [ + $non_writable_permission, + $non_writable_permission, + $non_writable_permission, + '', + [ + ValidationResult::createError([$project_root_error, $vendor_error], $summary), ], ], ]; @@ -73,20 +116,20 @@ class WritableFileSystemValidatorTest extends PackageManagerKernelTestBase { * * @param int $root_permissions * The file permissions for the root folder. + * @param int $webroot_permissions + * The file permissions for the web root folder. * @param int $vendor_permissions * The file permissions for the vendor folder. - * @param array $expected_results + * @param string $webroot_relative_directory + * The web root path, relative to the project root, or an empty string if + * the web root and project root are the same. + * @param \Drupal\package_manager\ValidationResult[] $expected_results * The expected validation results. * * @dataProvider providerWritable */ - public function testWritable(int $root_permissions, int $vendor_permissions, array $expected_results): void { - $path_locator = $this->container->get(PathLocator::class); - - // We need to set the vendor directory's permissions first because, in the - // test project, it's located inside the project root. - $this->assertTrue(chmod($path_locator->getVendorDirectory(), $vendor_permissions)); - $this->assertTrue(chmod($path_locator->getProjectRoot(), $root_permissions)); + public function testWritable(int $root_permissions, int $webroot_permissions, int $vendor_permissions, string $webroot_relative_directory, array $expected_results): void { + $this->setUpPermissions($root_permissions, $webroot_permissions, $vendor_permissions, $webroot_relative_directory); $this->assertStatusCheckResults($expected_results); $this->assertResults($expected_results, PreCreateEvent::class); @@ -97,24 +140,26 @@ class WritableFileSystemValidatorTest extends PackageManagerKernelTestBase { * * @param int $root_permissions * The file permissions for the root folder. + * @param int $webroot_permissions + * The file permissions for the web root folder. * @param int $vendor_permissions * The file permissions for the vendor folder. - * @param array $expected_results + * @param string $webroot_relative_directory + * The web root path, relative to the project root, or an empty string if + * the web root and project root are the same. + * @param \Drupal\package_manager\ValidationResult[] $expected_results * The expected validation results. * * @dataProvider providerWritable */ - public function testWritableDuringPreApply(int $root_permissions, int $vendor_permissions, array $expected_results): void { + public function testWritableDuringPreApply(int $root_permissions, int $webroot_permissions, int $vendor_permissions, string $webroot_relative_directory, array $expected_results): void { $this->addEventTestListener( - function () use ($root_permissions, $vendor_permissions): void { - $path_locator = $this->container->get(PathLocator::class); - - // We need to set the vendor directory's permissions first because, in - // the test project, it's located inside the project root. - $this->assertTrue(chmod($path_locator->getVendorDirectory(), $vendor_permissions)); - $this->assertTrue(chmod($path_locator->getProjectRoot(), $root_permissions)); + function () use ($webroot_permissions, $root_permissions, $vendor_permissions, $webroot_relative_directory): void { + $this->setUpPermissions($root_permissions, $webroot_permissions, $vendor_permissions, $webroot_relative_directory); // During pre-apply we don't care whether the staging root is writable. + /** @var \Drupal\package_manager_bypass\MockPathLocator $path_locator */ + $path_locator = $this->container->get(PathLocator::class); $this->assertTrue(chmod($path_locator->getStagingRoot(), 0550)); }, ); @@ -122,6 +167,41 @@ class WritableFileSystemValidatorTest extends PackageManagerKernelTestBase { $this->assertResults($expected_results, PreApplyEvent::class); } + /** + * Sets the permissions of the test project's directories. + * + * @param int $root_permissions + * The permissions for the project root. + * @param int $web_root_permissions + * The permissions for the web root. + * @param int $vendor_permissions + * The permissions for the vendor directory. + * @param string $relative_web_root + * The web root path, relative to the project root, or an empty string if + * the web root and project root are the same. + */ + private function setUpPermissions(int $root_permissions, int $web_root_permissions, int $vendor_permissions, string $relative_web_root): void { + /** @var \Drupal\package_manager_bypass\MockPathLocator $path_locator */ + $path_locator = $this->container->get(PathLocator::class); + + $project_root = $web_root = $path_locator->getProjectRoot(); + $vendor_dir = $path_locator->getVendorDirectory(); + // Create the web root directory, if necessary. + if (!empty($relative_web_root)) { + $web_root .= '/' . $relative_web_root; + mkdir($web_root); + } + $path_locator->setPaths($project_root, $vendor_dir, $relative_web_root, $path_locator->getStagingRoot()); + + // We need to set the vendor directory and web root permissions first + // because they may be located inside the project root. + $this->assertTrue(chmod($vendor_dir, $vendor_permissions)); + if ($project_root !== $web_root) { + $this->assertTrue(chmod($web_root, $web_root_permissions)); + } + $this->assertTrue(chmod($project_root, $root_permissions)); + } + /** * Data provider for ::testStagingRootPermissions(). *