diff --git a/core/lib/Drupal/Core/Updater/Module.php b/core/lib/Drupal/Core/Updater/Module.php index 06e1c1e..6c1c50a 100644 --- a/core/lib/Drupal/Core/Updater/Module.php +++ b/core/lib/Drupal/Core/Updater/Module.php @@ -20,7 +20,7 @@ class Module extends Updater implements UpdaterInterface { * * If the module is already installed, drupal_get_path() will return * a valid path and we should install it there (although we need to use an - * absolute path, so we prepend DRUPAL_ROOT). If we're installing a new + * absolute path, so we prepend the root path). If we're installing a new * module, we always want it to go into /modules, since that's * where all the documentation recommends users install their modules, and * there's no way that can conflict on a multi-site installation, since @@ -35,9 +35,16 @@ public function getInstallDirectory() { $relative_path = dirname($relative_path); } else { - $relative_path = 'modules'; + $relative_path = $this->getRootDirectoryRelativePath(); } - return DRUPAL_ROOT . '/' . $relative_path; + return $this->root . '/' . $relative_path; + } + + /** + * {@inheritdoc} + */ + public static function getRootDirectoryRelativePath() { + return 'modules'; } /** diff --git a/core/lib/Drupal/Core/Updater/Theme.php b/core/lib/Drupal/Core/Updater/Theme.php index b379325..03a03d9 100644 --- a/core/lib/Drupal/Core/Updater/Theme.php +++ b/core/lib/Drupal/Core/Updater/Theme.php @@ -20,7 +20,7 @@ class Theme extends Updater implements UpdaterInterface { * * If the theme is already installed, drupal_get_path() will return * a valid path and we should install it there (although we need to use an - * absolute path, so we prepend DRUPAL_ROOT). If we're installing a new + * absolute path, so we prepend the root path). If we're installing a new * theme, we always want it to go into /themes, since that's * where all the documentation recommends users install their themes, and * there's no way that can conflict on a multi-site installation, since @@ -35,9 +35,16 @@ public function getInstallDirectory() { $relative_path = dirname($relative_path); } else { - $relative_path = 'themes'; + $relative_path = $this->getRootDirectoryRelativePath(); } - return DRUPAL_ROOT . '/' . $relative_path; + return $this->root . '/' . $relative_path; + } + + /** + * {@inheritdoc} + */ + public static function getRootDirectoryRelativePath() { + return 'themes'; } /** diff --git a/core/lib/Drupal/Core/Updater/Updater.php b/core/lib/Drupal/Core/Updater/Updater.php index af14805..5e5fff9 100644 --- a/core/lib/Drupal/Core/Updater/Updater.php +++ b/core/lib/Drupal/Core/Updater/Updater.php @@ -24,13 +24,25 @@ class Updater { public $source; /** + * The root directory under which new projects will be copied. + * + * @var string + */ + protected $root; + + /** * Constructs a new updater. * * @param string $source * Directory to install from. + * @param string $root + * The root directory under which the project will be copied to if it's a + * new project. Usually this is the app root (the directory in which the + * Drupal site is installed). */ - public function __construct($source) { + public function __construct($source, $root) { $this->source = $source; + $this->root = $root; $this->name = self::getProjectName($source); $this->title = self::getProjectTitle($source); } @@ -43,20 +55,24 @@ public function __construct($source) { * * @param string $source * Directory of a Drupal project. + * @param string $root + * The root directory under which the project will be copied to if it's a + * new project. Usually this is the app root (the directory in which the + * Drupal site is installed). * * @return \Drupal\Core\Updater\Updater * A new Drupal\Core\Updater\Updater object. * * @throws \Drupal\Core\Updater\UpdaterException */ - public static function factory($source) { + public static function factory($source, $root) { if (is_dir($source)) { $updater = self::getUpdaterFromDirectory($source); } else { throw new UpdaterException(t('Unable to determine the type of the source directory.')); } - return new $updater($source); + return new $updater($source, $root); } /** diff --git a/core/lib/Drupal/Core/Updater/UpdaterInterface.php b/core/lib/Drupal/Core/Updater/UpdaterInterface.php index 50a1409..438c3a4 100644 --- a/core/lib/Drupal/Core/Updater/UpdaterInterface.php +++ b/core/lib/Drupal/Core/Updater/UpdaterInterface.php @@ -35,7 +35,7 @@ public function isInstalled(); public static function getProjectName($directory); /** - * Returns the path to the default install location. + * Returns the path to the default install location for the current project. * * @return string * An absolute path to the default install location. @@ -43,6 +43,14 @@ public static function getProjectName($directory); public function getInstallDirectory(); /** + * Returns the name of the root directory under which projects will be copied. + * + * @return string + * A relative path to the root directory. + */ + public static function getRootDirectoryRelativePath(); + + /** * Determines if the Updater can handle the project provided in $directory. * * @todo Provide something more rational here, like a project spec file. diff --git a/core/modules/update/src/Form/UpdateManagerInstall.php b/core/modules/update/src/Form/UpdateManagerInstall.php index b3e2deb..750e1c6 100644 --- a/core/modules/update/src/Form/UpdateManagerInstall.php +++ b/core/modules/update/src/Form/UpdateManagerInstall.php @@ -27,7 +27,7 @@ class UpdateManagerInstall extends FormBase { protected $moduleHandler; /** - * The app root. + * The root location under which installed projects will be saved. * * @var string */ @@ -37,7 +37,7 @@ class UpdateManagerInstall extends FormBase { * Constructs a new UpdateManagerInstall. * * @param string $root - * The app root. + * The root location under which installed projects will be saved. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler. */ @@ -58,7 +58,7 @@ public function getFormID() { */ public static function create(ContainerInterface $container) { return new static( - $container->get('app.root'), + $container->get('update.root'), $container->get('module_handler') ); } @@ -180,7 +180,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) { $project_location = $directory . '/' . $project; try { - $updater = Updater::factory($project_location); + $updater = Updater::factory($project_location, $this->root); } catch (\Exception $e) { drupal_set_message($e->getMessage(), 'error'); diff --git a/core/modules/update/src/Form/UpdateReady.php b/core/modules/update/src/Form/UpdateReady.php index 8e106fe..729265c 100644 --- a/core/modules/update/src/Form/UpdateReady.php +++ b/core/modules/update/src/Form/UpdateReady.php @@ -21,7 +21,7 @@ class UpdateReady extends FormBase { /** - * The app root. + * The root location under which updated projects will be saved. * * @var string */ @@ -45,7 +45,7 @@ class UpdateReady extends FormBase { * Constructs a new UpdateReady object. * * @param string $root - * The app root. + * The root location under which updated projects will be saved. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The object that manages enabled modules in a Drupal installation. * @param \Drupal\Core\State\StateInterface $state @@ -69,7 +69,7 @@ public function getFormID() { */ public static function create(ContainerInterface $container) { return new static( - $container->get('app.root'), + $container->get('update.root'), $container->get('module_handler'), $container->get('state') ); @@ -128,7 +128,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) { $project_real_location = NULL; foreach ($projects as $project => $url) { $project_location = $directory . '/' . $project; - $updater = Updater::factory($project_location); + $updater = Updater::factory($project_location, $this->root); $project_real_location = drupal_realpath($project_location); $updates[] = array( 'project' => $project, diff --git a/core/modules/update/src/Tests/UpdateTestBase.php b/core/modules/update/src/Tests/UpdateTestBase.php index 3d36aac..3306182 100644 --- a/core/modules/update/src/Tests/UpdateTestBase.php +++ b/core/modules/update/src/Tests/UpdateTestBase.php @@ -21,6 +21,7 @@ namespace Drupal\update\Tests; +use Drupal\Core\DrupalKernel; use Drupal\Core\Url; use Drupal\simpletest\WebTestBase; @@ -29,6 +30,29 @@ */ abstract class UpdateTestBase extends WebTestBase { + protected function setUp() { + parent::setUp(); + + // Change the root path which Update Manager uses to install and update + // projects to be inside the testing site directory. See + // \Drupal\updateUpdateRootFactory::get() for equivalent changes to the + // test child site. + $request = \Drupal::request(); + $update_root = $this->container->get('update.root') . '/' . DrupalKernel::findSitePath($request); + $this->container->set('update.root', $update_root); + \Drupal::setContainer($this->container); + + // Create the directories within the root path within which the Update + // Manager will install projects. + foreach (drupal_get_updaters() as $updater_info) { + $updater = $updater_info['class']; + $install_directory = $update_root . '/' . $updater::getRootDirectoryRelativePath(); + if (!is_dir($install_directory)) { + mkdir($install_directory); + } + } + } + /** * Refreshes the update status based on the desired available update scenario. * diff --git a/core/modules/update/src/Tests/UpdateUploadTest.php b/core/modules/update/src/Tests/UpdateUploadTest.php index 0565ff0..8f09736 100644 --- a/core/modules/update/src/Tests/UpdateUploadTest.php +++ b/core/modules/update/src/Tests/UpdateUploadTest.php @@ -45,6 +45,7 @@ public function testUploadModule() { // This also checks that the correct archive extensions are allowed. $this->drupalPostForm('admin/modules/install', $edit, t('Install')); $this->assertText(t('Only files with the following extensions are allowed: @archive_extensions.', array('@archive_extensions' => archiver_get_extensions())),'Only valid archives can be uploaded.'); + $this->assertUrl('admin/modules/install'); // Check to ensure an existing module can't be reinstalled. Also checks that // the archive was extracted since we can't know if the module is already @@ -55,6 +56,25 @@ public function testUploadModule() { ); $this->drupalPostForm('admin/modules/install', $edit, t('Install')); $this->assertText(t('@module_name is already installed.', array('@module_name' => 'AAA Update test')), 'Existing module was extracted and not reinstalled.'); + $this->assertUrl('admin/modules/install'); + + // Ensure that a new module can be extracted and installed. + $updaters = drupal_get_updaters(); + $moduleUpdater = $updaters['module']['class']; + $installedInfoFilePath = $this->container->get('update.root') . '/' . $moduleUpdater::getRootDirectoryRelativePath() . '/update_test_new_module/update_test_new_module.info.yml'; + $this->assertFalse(file_exists($installedInfoFilePath), 'The new module does not exist in the filesystem before it is installed with the Update Manager.'); + $validArchiveFile = drupal_get_path('module', 'update') . '/tests/update_test_new_module.tar.gz'; + $edit = array( + 'files[project_upload]' => $validArchiveFile, + ); + $this->drupalPostForm('admin/modules/install', $edit, t('Install')); + // Check that submitting the form takes the user to authorize.php. + // @todo Remove from the expected URL once that bug is fixed. + $this->assertUrl('core/authorize.php/'); + // Check for a success message on the page, and check that the installed + // module now exists in the expected place in the filesystem. + $this->assertRaw(t('Installed %project_name successfully', array('%project_name' => 'update_test_new_module'))); + $this->assertTrue(file_exists($installedInfoFilePath), 'The new module exists in the filesystem after it is installed with the Update Manager.'); } /** diff --git a/core/modules/update/src/UpdateRootFactory.php b/core/modules/update/src/UpdateRootFactory.php new file mode 100644 index 0000000..42a6d7c --- /dev/null +++ b/core/modules/update/src/UpdateRootFactory.php @@ -0,0 +1,74 @@ +drupalKernel = $drupal_kernel; + $this->requestStack = $request_stack; + } + + /** + * Gets the root path under which projects are installed or updated. + * + * The Update Manager will ensure that project files can only be copied to + * specific subdirectories of this root path. + * + * @return string + */ + public function get() { + // Normally the Update Manager's root path is the same as the app root (the + // directory in which the Drupal site is installed). + $root_path = $this->drupalKernel->getAppRoot(); + + // When running in a test site, change the root path to be the testing site + // directory. This ensures that it will always be writable by the webserver + // (thereby allowing the actual extraction and installation of projects by + // the Update Manager to be tested) and also ensures that new project files + // added there won't be visible to the parent site and will be properly + // cleaned up once the test finishes running. See also + // \Drupal\update\Tests\UpdateTestBase::setUp(). + if (DRUPAL_TEST_IN_CHILD_SITE) { + $kernel = $this->drupalKernel; + $request = $this->requestStack->getCurrentRequest(); + $root_path .= '/' . $kernel::findSitePath($request); + } + + return $root_path; + } + +} diff --git a/core/modules/update/tests/update_test_new_module.tar.gz b/core/modules/update/tests/update_test_new_module.tar.gz new file mode 100644 index 0000000..fba2143 --- /dev/null +++ b/core/modules/update/tests/update_test_new_module.tar.gz @@ -0,0 +1 @@ +wTAO0+z#0ڲƘA^4JV`m֪{;"&!A}Bulogյm[ApHS*nFb9v."ŤJy$8AIEiVsO۞0+e] XhgB2~疿:$ʛ6yS7m4~FR]Oج,)#gLY"mߚn,+fadFyUat{G&ewrrb;{Ul_(}d fn4=]/7.3]Qf]Ch,}\( \ No newline at end of file diff --git a/core/modules/update/update.authorize.inc b/core/modules/update/update.authorize.inc index d6a7066..9ff8d02 100644 --- a/core/modules/update/update.authorize.inc +++ b/core/modules/update/update.authorize.inc @@ -147,7 +147,7 @@ function update_authorize_batch_copy_project($project, $updater_name, $local_url return; } - $updater = new $updater_name($local_url); + $updater = new $updater_name($local_url, \Drupal::getContainer()->get('update.root')); try { if ($updater->isInstalled()) { diff --git a/core/modules/update/update.services.yml b/core/modules/update/update.services.yml index fe01f47..3286b2a 100644 --- a/core/modules/update/update.services.yml +++ b/core/modules/update/update.services.yml @@ -13,3 +13,12 @@ services: update.fetcher: class: Drupal\update\UpdateFetcher arguments: ['@config.factory', '@http_client'] + update.root: + class: SplString + factory_service: 'update.root.factory' + factory_method: 'get' + tags: + - { name: parameter_service } + update.root.factory: + class: Drupal\update\UpdateRootFactory + arguments: ['@kernel', '@request_stack']