diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index 2294d47..ec12097 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -151,10 +151,11 @@ function config_get_config_directory($type) { } if (!empty($config_directories[$type])) { return $config_directories[$type]; } + // @todo https://www.drupal.org/node/2696103 Throw a more specific exception. throw new \Exception("The configuration directory type '$type' does not exist"); } /** * Returns and optionally sets the filename for a system resource. diff --git a/core/includes/file.inc b/core/includes/file.inc index 3e5eb8c..b38efb8 100644 --- a/core/includes/file.inc +++ b/core/includes/file.inc @@ -330,11 +330,23 @@ function file_ensure_htaccess() { $private_path = PrivateStream::basePath(); if (!empty($private_path)) { file_save_htaccess('private://', TRUE); } file_save_htaccess('temporary://', TRUE); - file_save_htaccess(config_get_config_directory(CONFIG_SYNC_DIRECTORY), TRUE); + + // If a staging directory exists then it should contain a .htaccess file. + // @todo https://www.drupal.org/node/2696103 catch a more specific exception + // and simplify this code. + try { + $staging = config_get_config_directory(CONFIG_SYNC_DIRECTORY); + } + catch (\Exception $e) { + $staging = FALSE; + } + if ($staging) { + file_save_htaccess($staging, TRUE); + } } /** * Creates a .htaccess file in the given directory. * diff --git a/core/modules/config/config.install b/core/modules/config/config.install new file mode 100644 index 0000000..c971ae6 --- /dev/null +++ b/core/modules/config/config.install @@ -0,0 +1,32 @@ + t('Configuration directory: %type', ['%type' => CONFIG_SYNC_DIRECTORY]), + 'description' => t('The directory %directory is not writable.', ['%directory' => $directory]), + 'severity' => REQUIREMENT_WARNING, + ]; + } + return $requirements; +} diff --git a/core/modules/config/src/Form/ConfigImportForm.php b/core/modules/config/src/Form/ConfigImportForm.php index f95b7a5..1d651d8 100644 --- a/core/modules/config/src/Form/ConfigImportForm.php +++ b/core/modules/config/src/Form/ConfigImportForm.php @@ -48,19 +48,25 @@ public function getFormId() { /** * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state) { + $directory = config_get_config_directory(CONFIG_SYNC_DIRECTORY); + $directory_is_writable = is_writable($directory); + if (!$directory_is_writable) { + drupal_set_message($this->t('The directory %directory is not writable.', ['%directory' => $directory]), 'error'); + } $form['import_tarball'] = array( '#type' => 'file', '#title' => $this->t('Configuration archive'), '#description' => $this->t('Allowed types: @extensions.', array('@extensions' => 'tar.gz tgz tar.bz2')), ); $form['submit'] = array( '#type' => 'submit', '#value' => $this->t('Upload'), + '#disabled' => !$directory_is_writable, ); return $form; } /** diff --git a/core/modules/config/src/Tests/ConfigImportUploadTest.php b/core/modules/config/src/Tests/ConfigImportUploadTest.php index 445ca9f..9836d77 100644 --- a/core/modules/config/src/Tests/ConfigImportUploadTest.php +++ b/core/modules/config/src/Tests/ConfigImportUploadTest.php @@ -43,8 +43,18 @@ function testImport() { // Attempt to upload a non-tar file. $text_file = current($this->drupalGetTestFiles('text')); $edit = array('files[import_tarball]' => drupal_realpath($text_file->uri)); $this->drupalPostForm('admin/config/development/configuration/full/import', $edit, t('Upload')); $this->assertText(t('Could not extract the contents of the tar file')); + + // Make the sync directory read-only. + $directory = config_get_config_directory(CONFIG_SYNC_DIRECTORY); + \Drupal::service('file_system')->chmod($directory, 0555); + $this->drupalGet('admin/config/development/configuration/full/import'); + $this->assertRaw(t('The directory %directory is not writable.', ['%directory' => $directory])); + // Ensure submit button for \Drupal\config\Form\ConfigImportForm is + // disabled. + $submit_is_disabled = $this->cssSelect('form.config-import-form input[type="submit"]:disabled'); + $this->assertTrue(count($submit_is_disabled) === 1, 'The submit button is disabled.'); } } diff --git a/core/modules/config/src/Tests/ConfigInstallWebTest.php b/core/modules/config/src/Tests/ConfigInstallWebTest.php index 4b18a4c..fc6cee4 100644 --- a/core/modules/config/src/Tests/ConfigInstallWebTest.php +++ b/core/modules/config/src/Tests/ConfigInstallWebTest.php @@ -24,11 +24,11 @@ class ConfigInstallWebTest extends WebTestBase { * {@inheritdoc} */ protected function setUp() { parent::setUp(); - $this->adminUser = $this->drupalCreateUser(array('administer modules', 'administer themes')); + $this->adminUser = $this->drupalCreateUser(array('administer modules', 'administer themes', 'administer site configuration')); // Ensure the global variable being asserted by this test does not exist; // a previous test executed in this request/process might have set it. unset($GLOBALS['hook_config_test']); } @@ -186,6 +186,24 @@ public function testUnmetDependenciesInstall() { $this->drupalPostForm('admin/modules', array('modules[Testing][config_install_dependency_test][enable]' => TRUE), t('Install')); $this->rebuildContainer(); $this->assertTrue(entity_load('config_test', 'other_module_test_with_dependency'), 'The config_test.dynamic.other_module_test_with_dependency configuration has been created during install.'); } + /** + * Tests config_requirements(). + */ + public function testConfigModuleRequirements() { + $this->drupalLogin($this->adminUser); + $this->drupalPostForm('admin/modules', array('modules[Core][config][enable]' => TRUE), t('Install')); + + $directory = config_get_config_directory(CONFIG_SYNC_DIRECTORY); + file_unmanaged_delete_recursive($directory); + $this->drupalGet('/admin/reports/status'); + $this->assertRaw(t('The directory %directory does not exist.', array('%directory' => $directory))); + + file_prepare_directory($directory, FILE_CREATE_DIRECTORY); + \Drupal::service('file_system')->chmod($directory, 0555); + $this->drupalGet('/admin/reports/status'); + $this->assertRaw(t('The directory %directory is not writable.', ['%directory' => $directory])); + } + } diff --git a/core/modules/system/src/Tests/System/StatusTest.php b/core/modules/system/src/Tests/System/StatusTest.php index 525b079..c170316 100644 --- a/core/modules/system/src/Tests/System/StatusTest.php +++ b/core/modules/system/src/Tests/System/StatusTest.php @@ -22,10 +22,20 @@ class StatusTest extends WebTestBase { * {@inheritdoc} */ protected function setUp() { parent::setUp(); + // Unset the sync directory in settings.php to trigger $config_directories + // error. + $settings['config_directories'] = array( + CONFIG_SYNC_DIRECTORY => (object) array( + 'value' => '', + 'required' => TRUE, + ), + ); + $this->writeSettings($settings); + $admin_user = $this->drupalCreateUser(array( 'administer site configuration', )); $this->drupalLogin($admin_user); } @@ -58,10 +68,13 @@ public function testStatusPage() { } // If a module is fully installed no pending updates exists. $this->assertNoText(t('Out of date')); + // The global $config_directories is not properly formed. + $this->assertRaw(t('Your %file file must define the $config_directories variable as an array containing the names of directories in which configuration files can be found. It must contain a %sync_key key.', array('%file' => $this->siteDirectory . '/settings.php', '%sync_key' => CONFIG_SYNC_DIRECTORY))); + // Set the schema version of update_test_postupdate to a lower version, so // update_test_postupdate_update_8001() needs to be executed. drupal_set_installed_schema_version('update_test_postupdate', 8000); $this->drupalGet('admin/reports/status'); $this->assertText(t('Out of date')); diff --git a/core/modules/system/system.install b/core/modules/system/system.install index cdaeba6..b8f5f5f 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -547,19 +547,26 @@ function system_requirements($phase) { // Check the config directory if it is defined in settings.php. If it isn't // defined, the installer will create a valid config directory later, but // during runtime we must always display an error. if (!empty($GLOBALS['config_directories'])) { - foreach ($GLOBALS['config_directories'] as $type => $directory) { - $directories[] = config_get_config_directory($type); + foreach (array_keys(array_filter($GLOBALS['config_directories'])) as $type) { + $directory = config_get_config_directory($type); + if (!is_dir($directory)) { + $requirements['config directory ' . $type] = array( + 'title' => t('Configuration directory: %type', ['%type' => $type]), + 'description' => t('The directory %directory does not exist.', array('%directory' => $directory)), + 'severity' => REQUIREMENT_ERROR, + ); + } } } - elseif ($phase != 'install') { + if ($phase != 'install' && (empty($GLOBALS['config_directories']) || empty($GLOBALS['config_directories'][CONFIG_SYNC_DIRECTORY]) )) { $requirements['config directories'] = array( 'title' => t('Configuration directories'), 'value' => t('Not present'), - 'description' => t('Your %file file must define the $config_directories variable as an array containing the name of a directories in which configuration files can be written.', array('%file' => $site_path . '/settings.php')), + 'description' => t('Your %file file must define the $config_directories variable as an array containing the names of directories in which configuration files can be found. It must contain a %sync_key key.', array('%file' => $site_path . '/settings.php', '%sync_key' => CONFIG_SYNC_DIRECTORY)), 'severity' => REQUIREMENT_ERROR, ); } $requirements['file system'] = array(