Issue #2788777 by alexpott, bircher, jribeiro, Eli-T, mpotter, douggreen, GoZ, DamienMcKenna, Dane Powell, jibran, szeidler, Alumei, andypost, dawehner, johndevman: Allow a site-specific profile to be installed from existing config
parent
34637de7aa
commit
de724027fe
|
@ -6,6 +6,11 @@
|
|||
*/
|
||||
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\Batch\BatchBuilder;
|
||||
use Drupal\Core\Config\ConfigImporter;
|
||||
use Drupal\Core\Config\ConfigImporterException;
|
||||
use Drupal\Core\Config\Importer\ConfigImporterBatch;
|
||||
use Drupal\Core\Config\StorageComparer;
|
||||
use Drupal\Core\DrupalKernel;
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Database\DatabaseExceptionWrapper;
|
||||
|
@ -198,6 +203,10 @@ function install_state_defaults() {
|
|||
// The last task that was completed during the previous installation
|
||||
// request.
|
||||
'completed_task' => NULL,
|
||||
// Partial configuration cached during an installation from existing config.
|
||||
'config' => NULL,
|
||||
// The path to the configuration to install when installing from config.
|
||||
'config_install_path' => NULL,
|
||||
// TRUE when there are valid config directories.
|
||||
'config_verified' => FALSE,
|
||||
// TRUE when there is a valid database connection.
|
||||
|
@ -473,9 +482,13 @@ function install_begin_request($class_loader, &$install_state) {
|
|||
// @todo Remove as part of https://www.drupal.org/node/2186491
|
||||
drupal_get_filename('module', 'system', 'core/modules/system/system.info.yml');
|
||||
|
||||
// Use the language from the profile configuration, if available, to override
|
||||
// the language previously set in the parameters.
|
||||
if (isset($install_state['profile_info']['distribution']['langcode'])) {
|
||||
// Use the language from profile configuration if available.
|
||||
if (!empty($install_state['config_install_path']) && $install_state['config']['system.site']) {
|
||||
$install_state['parameters']['langcode'] = $install_state['config']['system.site']['default_langcode'];
|
||||
}
|
||||
elseif (isset($install_state['profile_info']['distribution']['langcode'])) {
|
||||
// Otherwise, Use the language from the profile configuration, if available,
|
||||
// to override the language previously set in the parameters.
|
||||
$install_state['parameters']['langcode'] = $install_state['profile_info']['distribution']['langcode'];
|
||||
}
|
||||
|
||||
|
@ -818,6 +831,30 @@ function install_tasks($install_state) {
|
|||
],
|
||||
];
|
||||
|
||||
if (!empty($install_state['config_install_path'])) {
|
||||
// The chosen profile indicates that rather than installing a new site, an
|
||||
// instance of the same site should be installed from the given
|
||||
// configuration.
|
||||
// That means we need to remove the steps installing the extensions and
|
||||
// replace them with a configuration synchronization step.
|
||||
unset($tasks['install_download_translation']);
|
||||
$key = array_search('install_profile_modules', array_keys($tasks), TRUE);
|
||||
unset($tasks['install_profile_modules']);
|
||||
unset($tasks['install_profile_themes']);
|
||||
unset($tasks['install_install_profile']);
|
||||
$config_tasks = [
|
||||
'install_config_import_batch' => [
|
||||
'display_name' => t('Install configuration'),
|
||||
'type' => 'batch',
|
||||
],
|
||||
'install_config_download_translations' => [],
|
||||
'install_config_revert_install_changes' => [],
|
||||
];
|
||||
$tasks = array_slice($tasks, 0, $key, TRUE) +
|
||||
$config_tasks +
|
||||
array_slice($tasks, $key, NULL, TRUE);
|
||||
}
|
||||
|
||||
// Now add any tasks defined by the installation profile.
|
||||
if (!empty($install_state['parameters']['profile'])) {
|
||||
// Load the profile install file, because it is not always loaded when
|
||||
|
@ -1494,6 +1531,14 @@ function install_load_profile(&$install_state) {
|
|||
$profile = $install_state['parameters']['profile'];
|
||||
$install_state['profiles'][$profile]->load();
|
||||
$install_state['profile_info'] = install_profile_info($profile, isset($install_state['parameters']['langcode']) ? $install_state['parameters']['langcode'] : 'en');
|
||||
// If the profile has a config/sync directory copy the information to the
|
||||
// install_state global.
|
||||
if (!empty($install_state['profile_info']['config_install_path'])) {
|
||||
$install_state['config_install_path'] = $install_state['profile_info']['config_install_path'];
|
||||
if (!empty($install_state['profile_info']['config'])) {
|
||||
$install_state['config'] = $install_state['profile_info']['config'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2260,3 +2305,134 @@ function install_write_profile($install_state) {
|
|||
throw new InstallProfileMismatchException($install_state['parameters']['profile'], $settings_profile, $settings_path, \Drupal::translation());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a batch for the config importer to process.
|
||||
*
|
||||
* @see install_tasks()
|
||||
*/
|
||||
function install_config_import_batch() {
|
||||
// We need to manually trigger the installation of core-provided entity types,
|
||||
// as those will not be handled by the module installer.
|
||||
// @see install_profile_modules()
|
||||
install_core_entity_type_definitions();
|
||||
|
||||
// Get the sync storage.
|
||||
$sync = \Drupal::service('config.storage.sync');
|
||||
// Match up the site UUIDs, the install_base_system install task will have
|
||||
// installed the system module and created a new UUID.
|
||||
$system_site = $sync->read('system.site');
|
||||
\Drupal::configFactory()->getEditable('system.site')->set('uuid', $system_site['uuid'])->save();
|
||||
|
||||
// Create the storage comparer and the config importer.
|
||||
$config_manager = \Drupal::service('config.manager');
|
||||
$storage_comparer = new StorageComparer($sync, \Drupal::service('config.storage'), $config_manager);
|
||||
$storage_comparer->createChangelist();
|
||||
$config_importer = new ConfigImporter(
|
||||
$storage_comparer,
|
||||
\Drupal::service('event_dispatcher'),
|
||||
$config_manager,
|
||||
\Drupal::service('lock.persistent'),
|
||||
\Drupal::service('config.typed'),
|
||||
\Drupal::service('module_handler'),
|
||||
\Drupal::service('module_installer'),
|
||||
\Drupal::service('theme_handler'),
|
||||
\Drupal::service('string_translation')
|
||||
);
|
||||
|
||||
try {
|
||||
$sync_steps = $config_importer->initialize();
|
||||
|
||||
$batch_builder = new BatchBuilder();
|
||||
$batch_builder
|
||||
->setFinishCallback([ConfigImporterBatch::class, 'finish'])
|
||||
->setTitle(t('Importing configuration'))
|
||||
->setInitMessage(t('Starting configuration import.'))
|
||||
->setErrorMessage(t('Configuration import has encountered an error.'));
|
||||
|
||||
foreach ($sync_steps as $sync_step) {
|
||||
$batch_builder->addOperation([ConfigImporterBatch::class, 'process'], [$config_importer, $sync_step]);
|
||||
}
|
||||
|
||||
return $batch_builder->toArray();
|
||||
}
|
||||
catch (ConfigImporterException $e) {
|
||||
global $install_state;
|
||||
// There are validation errors.
|
||||
$messenger = \Drupal::messenger();
|
||||
$messenger->addError(t('The configuration synchronization failed validation.'));
|
||||
foreach ($config_importer->getErrors() as $message) {
|
||||
$messenger->addError($message);
|
||||
}
|
||||
install_display_output(['#title' => t('Configuration validation')], $install_state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces install_download_translation() during configuration installs.
|
||||
*
|
||||
* @param array $install_state
|
||||
* An array of information about the current installation state.
|
||||
*
|
||||
* @return string
|
||||
* A themed status report, or an exception if there are requirement errors.
|
||||
* Upon successful download the page is reloaded and no output is returned.
|
||||
*
|
||||
* @see install_download_translation()
|
||||
*/
|
||||
function install_config_download_translations(&$install_state) {
|
||||
$needs_download = isset($install_state['parameters']['langcode']) && !isset($install_state['translations'][$install_state['parameters']['langcode']]) && $install_state['parameters']['langcode'] !== 'en';
|
||||
if ($needs_download) {
|
||||
return install_download_translation($install_state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverts configuration if hook_install() implementations have made changes.
|
||||
*
|
||||
* This step ensures that the final configuration matches the configuration
|
||||
* provided to the installer.
|
||||
*/
|
||||
function install_config_revert_install_changes() {
|
||||
global $install_state;
|
||||
|
||||
$config_manager = \Drupal::service('config.manager');
|
||||
$storage_comparer = new StorageComparer(\Drupal::service('config.storage.sync'), \Drupal::service('config.storage'), $config_manager);
|
||||
$storage_comparer->createChangelist();
|
||||
if ($storage_comparer->hasChanges()) {
|
||||
$config_importer = new ConfigImporter(
|
||||
$storage_comparer,
|
||||
\Drupal::service('event_dispatcher'),
|
||||
$config_manager,
|
||||
\Drupal::service('lock.persistent'),
|
||||
\Drupal::service('config.typed'),
|
||||
\Drupal::service('module_handler'),
|
||||
\Drupal::service('module_installer'),
|
||||
\Drupal::service('theme_handler'),
|
||||
\Drupal::service('string_translation')
|
||||
);
|
||||
try {
|
||||
$config_importer->import();
|
||||
}
|
||||
catch (ConfigImporterException $e) {
|
||||
global $install_state;
|
||||
$messenger = \Drupal::messenger();
|
||||
// There are validation errors.
|
||||
$messenger->addError(t('The configuration synchronization failed validation.'));
|
||||
foreach ($config_importer->getErrors() as $message) {
|
||||
$messenger->addError($message);
|
||||
}
|
||||
install_display_output(['#title' => t('Configuration validation')], $install_state);
|
||||
}
|
||||
|
||||
// At this point the configuration should match completely.
|
||||
if (\Drupal::moduleHandler()->moduleExists('language')) {
|
||||
// If the English language exists at this point we need to ensure
|
||||
// install_download_additional_translations_operations() does not delete
|
||||
// it.
|
||||
if (ConfigurableLanguage::load('en')) {
|
||||
$install_state['profile_info']['keep_english'] = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ use Symfony\Component\HttpFoundation\RedirectResponse;
|
|||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\Component\Utility\OpCodeCache;
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\Config\FileStorage;
|
||||
use Drupal\Core\Extension\ExtensionDiscovery;
|
||||
use Drupal\Core\Site\Settings;
|
||||
|
||||
|
@ -481,12 +482,20 @@ function _drupal_rewrite_settings_dump_one(\stdClass $variable, $prefix = '', $s
|
|||
* @see update_prepare_d8_bootstrap()
|
||||
*/
|
||||
function drupal_install_config_directories() {
|
||||
global $config_directories;
|
||||
global $config_directories, $install_state;
|
||||
|
||||
// Add a randomized config directory name to settings.php, unless it was
|
||||
// manually defined in the existing already.
|
||||
// If settings.php does not contain a config sync directory name we need to
|
||||
// configure one.
|
||||
if (empty($config_directories[CONFIG_SYNC_DIRECTORY])) {
|
||||
$config_directories[CONFIG_SYNC_DIRECTORY] = \Drupal::service('site.path') . '/files/config_' . Crypt::randomBytesBase64(55) . '/sync';
|
||||
if (empty($install_state['config_install_path'])) {
|
||||
// Add a randomized config directory name to settings.php
|
||||
$config_directories[CONFIG_SYNC_DIRECTORY] = \Drupal::service('site.path') . '/files/config_' . Crypt::randomBytesBase64(55) . '/sync';
|
||||
}
|
||||
else {
|
||||
// Install profiles can contain a config sync directory. If they do,
|
||||
// 'config_install_path' is a path to the directory.
|
||||
$config_directories[CONFIG_SYNC_DIRECTORY] = $install_state['config_install_path'];
|
||||
}
|
||||
$settings['config_directories'][CONFIG_SYNC_DIRECTORY] = (object) [
|
||||
'value' => $config_directories[CONFIG_SYNC_DIRECTORY],
|
||||
'required' => TRUE,
|
||||
|
@ -1099,9 +1108,10 @@ function install_profile_info($profile, $langcode = 'en') {
|
|||
'version' => NULL,
|
||||
'hidden' => FALSE,
|
||||
'php' => DRUPAL_MINIMUM_PHP,
|
||||
'config_install_path' => NULL,
|
||||
];
|
||||
$profile_file = drupal_get_path('profile', $profile) . "/$profile.info.yml";
|
||||
$info = \Drupal::service('info_parser')->parse($profile_file);
|
||||
$profile_path = drupal_get_path('profile', $profile);
|
||||
$info = \Drupal::service('info_parser')->parse("$profile_path/$profile.info.yml");
|
||||
$info += $defaults;
|
||||
|
||||
// drupal_required_modules() includes the current profile as a dependency.
|
||||
|
@ -1114,6 +1124,12 @@ function install_profile_info($profile, $langcode = 'en') {
|
|||
// remove any duplicates.
|
||||
$info['install'] = array_unique(array_merge($info['install'], $required, $info['dependencies'], $locale));
|
||||
|
||||
// If the profile has a config/sync directory use that to install drupal.
|
||||
if (is_dir($profile_path . '/config/sync')) {
|
||||
$info['config_install_path'] = $profile_path . '/config/sync';
|
||||
$sync = new FileStorage($profile_path . '/config/sync');
|
||||
$info['config']['system.site'] = $sync->read('system.site');
|
||||
}
|
||||
$cache[$profile][$langcode] = $info;
|
||||
}
|
||||
return $cache[$profile][$langcode];
|
||||
|
|
|
@ -405,6 +405,14 @@ class ConfigImporter {
|
|||
$module_list = array_reverse($module_list);
|
||||
$this->extensionChangelist['module']['install'] = array_intersect(array_keys($module_list), $install);
|
||||
|
||||
// If we're installing the install profile ensure it comes last. This will
|
||||
// occur when installing a site from configuration.
|
||||
$install_profile_key = array_search($new_extensions['profile'], $this->extensionChangelist['module']['install'], TRUE);
|
||||
if ($install_profile_key !== FALSE) {
|
||||
unset($this->extensionChangelist['module']['install'][$install_profile_key]);
|
||||
$this->extensionChangelist['module']['install'][] = $new_extensions['profile'];
|
||||
}
|
||||
|
||||
// Work out what themes to install and to uninstall.
|
||||
$this->extensionChangelist['theme']['install'] = array_keys(array_diff_key($new_extensions['theme'], $current_extensions['theme']));
|
||||
$this->extensionChangelist['theme']['uninstall'] = array_keys(array_diff_key($current_extensions['theme'], $new_extensions['theme']));
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Config\Importer;
|
||||
|
||||
use Drupal\Core\Config\ConfigImporter;
|
||||
|
||||
/**
|
||||
* Methods for running the ConfigImporter in a batch.
|
||||
*
|
||||
* @see \Drupal\Core\Config\ConfigImporter
|
||||
*/
|
||||
class ConfigImporterBatch {
|
||||
|
||||
/**
|
||||
* Processes the config import batch and persists the importer.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigImporter $config_importer
|
||||
* The batch config importer object to persist.
|
||||
* @param string $sync_step
|
||||
* The synchronization step to do.
|
||||
* @param array $context
|
||||
* The batch context.
|
||||
*/
|
||||
public static function process(ConfigImporter $config_importer, $sync_step, &$context) {
|
||||
if (!isset($context['sandbox']['config_importer'])) {
|
||||
$context['sandbox']['config_importer'] = $config_importer;
|
||||
}
|
||||
|
||||
$config_importer = $context['sandbox']['config_importer'];
|
||||
$config_importer->doSyncStep($sync_step, $context);
|
||||
if ($errors = $config_importer->getErrors()) {
|
||||
if (!isset($context['results']['errors'])) {
|
||||
$context['results']['errors'] = [];
|
||||
}
|
||||
$context['results']['errors'] = array_merge($errors, $context['results']['errors']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish batch.
|
||||
*
|
||||
* This function is a static function to avoid serializing the ConfigSync
|
||||
* object unnecessarily.
|
||||
*
|
||||
* @param bool $success
|
||||
* Indicate that the batch API tasks were all completed successfully.
|
||||
* @param array $results
|
||||
* An array of all the results that were updated in update_do_one().
|
||||
* @param array $operations
|
||||
* A list of the operations that had not been completed by the batch API.
|
||||
*/
|
||||
public static function finish($success, $results, $operations) {
|
||||
$messenger = \Drupal::messenger();
|
||||
if ($success) {
|
||||
if (!empty($results['errors'])) {
|
||||
$logger = \Drupal::logger('config_sync');
|
||||
foreach ($results['errors'] as $error) {
|
||||
$messenger->addError($error);
|
||||
$logger->error($error);
|
||||
}
|
||||
$messenger->addWarning(t('The configuration was imported with errors.'));
|
||||
}
|
||||
elseif (!drupal_installation_attempted()) {
|
||||
// Display a success message when not installing Drupal.
|
||||
$messenger->addStatus(t('The configuration was imported successfully.'));
|
||||
}
|
||||
}
|
||||
else {
|
||||
// An error occurred.
|
||||
// $operations contains the operations that remained unprocessed.
|
||||
$error_operation = reset($operations);
|
||||
$message = t('An error occurred while processing %error_operation with arguments: @arguments', ['%error_operation' => $error_operation[0], '@arguments' => print_r($error_operation[1], TRUE)]);
|
||||
$messenger->addError($message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -121,6 +121,7 @@ class SiteConfigureForm extends ConfigFormBase {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
global $install_state;
|
||||
$form['#title'] = $this->t('Configure site');
|
||||
|
||||
// Warn about settings.php permissions risk
|
||||
|
@ -148,12 +149,14 @@ class SiteConfigureForm extends ConfigFormBase {
|
|||
$form['site_information'] = [
|
||||
'#type' => 'fieldgroup',
|
||||
'#title' => $this->t('Site information'),
|
||||
'#access' => empty($install_state['config_install_path']),
|
||||
];
|
||||
$form['site_information']['site_name'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Site name'),
|
||||
'#required' => TRUE,
|
||||
'#weight' => -20,
|
||||
'#access' => empty($install_state['config_install_path']),
|
||||
];
|
||||
$form['site_information']['site_mail'] = [
|
||||
'#type' => 'email',
|
||||
|
@ -162,6 +165,7 @@ class SiteConfigureForm extends ConfigFormBase {
|
|||
'#description' => $this->t("Automated emails, such as registration information, will be sent from this address. Use an address ending in your site's domain to help prevent these emails from being flagged as spam."),
|
||||
'#required' => TRUE,
|
||||
'#weight' => -15,
|
||||
'#access' => empty($install_state['config_install_path']),
|
||||
];
|
||||
|
||||
$form['admin_account'] = [
|
||||
|
@ -191,6 +195,7 @@ class SiteConfigureForm extends ConfigFormBase {
|
|||
$form['regional_settings'] = [
|
||||
'#type' => 'fieldgroup',
|
||||
'#title' => $this->t('Regional settings'),
|
||||
'#access' => empty($install_state['config_install_path']),
|
||||
];
|
||||
$countries = $this->countryManager->getList();
|
||||
$form['regional_settings']['site_default_country'] = [
|
||||
|
@ -201,6 +206,7 @@ class SiteConfigureForm extends ConfigFormBase {
|
|||
'#options' => $countries,
|
||||
'#description' => $this->t('Select the default country for the site.'),
|
||||
'#weight' => 0,
|
||||
'#access' => empty($install_state['config_install_path']),
|
||||
];
|
||||
$form['regional_settings']['date_default_timezone'] = [
|
||||
'#type' => 'select',
|
||||
|
@ -211,17 +217,20 @@ class SiteConfigureForm extends ConfigFormBase {
|
|||
'#description' => $this->t('By default, dates in this site will be displayed in the chosen time zone.'),
|
||||
'#weight' => 5,
|
||||
'#attributes' => ['class' => ['timezone-detect']],
|
||||
'#access' => empty($install_state['config_install_path']),
|
||||
];
|
||||
|
||||
$form['update_notifications'] = [
|
||||
'#type' => 'fieldgroup',
|
||||
'#title' => $this->t('Update notifications'),
|
||||
'#description' => $this->t('The system will notify you when updates and important security releases are available for installed components. Anonymous information about your site is sent to <a href=":drupal">Drupal.org</a>.', [':drupal' => 'https://www.drupal.org']),
|
||||
'#access' => empty($install_state['config_install_path']),
|
||||
];
|
||||
$form['update_notifications']['enable_update_status_module'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Check for updates automatically'),
|
||||
'#default_value' => 1,
|
||||
'#access' => empty($install_state['config_install_path']),
|
||||
];
|
||||
$form['update_notifications']['enable_update_status_emails'] = [
|
||||
'#type' => 'checkbox',
|
||||
|
@ -232,6 +241,7 @@ class SiteConfigureForm extends ConfigFormBase {
|
|||
'input[name="enable_update_status_module"]' => ['checked' => TRUE],
|
||||
],
|
||||
],
|
||||
'#access' => empty($install_state['config_install_path']),
|
||||
];
|
||||
|
||||
$form['actions'] = ['#type' => 'actions'];
|
||||
|
@ -258,21 +268,25 @@ class SiteConfigureForm extends ConfigFormBase {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$this->config('system.site')
|
||||
->set('name', (string) $form_state->getValue('site_name'))
|
||||
->set('mail', (string) $form_state->getValue('site_mail'))
|
||||
->save(TRUE);
|
||||
global $install_state;
|
||||
|
||||
$this->config('system.date')
|
||||
->set('timezone.default', (string) $form_state->getValue('date_default_timezone'))
|
||||
->set('country.default', (string) $form_state->getValue('site_default_country'))
|
||||
->save(TRUE);
|
||||
if (empty($install_state['config_install_path'])) {
|
||||
$this->config('system.site')
|
||||
->set('name', (string) $form_state->getValue('site_name'))
|
||||
->set('mail', (string) $form_state->getValue('site_mail'))
|
||||
->save(TRUE);
|
||||
|
||||
$this->config('system.date')
|
||||
->set('timezone.default', (string) $form_state->getValue('date_default_timezone'))
|
||||
->set('country.default', (string) $form_state->getValue('site_default_country'))
|
||||
->save(TRUE);
|
||||
}
|
||||
|
||||
$account_values = $form_state->getValue('account');
|
||||
|
||||
// Enable update.module if this option was selected.
|
||||
$update_status_module = $form_state->getValue('enable_update_status_module');
|
||||
if ($update_status_module) {
|
||||
if (empty($install_state['config_install_path']) && $update_status_module) {
|
||||
$this->moduleInstaller->install(['file', 'update'], FALSE);
|
||||
|
||||
// Add the site maintenance account's email address to the list of
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace Drupal\config\Form;
|
|||
|
||||
use Drupal\Core\Config\ConfigImporterException;
|
||||
use Drupal\Core\Config\ConfigImporter;
|
||||
use Drupal\Core\Config\Importer\ConfigImporterBatch;
|
||||
use Drupal\Core\Config\TypedConfigManagerInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Extension\ModuleInstallerInterface;
|
||||
|
@ -337,14 +338,14 @@ class ConfigSync extends FormBase {
|
|||
$sync_steps = $config_importer->initialize();
|
||||
$batch = [
|
||||
'operations' => [],
|
||||
'finished' => [get_class($this), 'finishBatch'],
|
||||
'finished' => [ConfigImporterBatch::class, 'finish'],
|
||||
'title' => t('Synchronizing configuration'),
|
||||
'init_message' => t('Starting configuration synchronization.'),
|
||||
'progress_message' => t('Completed step @current of @total.'),
|
||||
'error_message' => t('Configuration synchronization has encountered an error.'),
|
||||
];
|
||||
foreach ($sync_steps as $sync_step) {
|
||||
$batch['operations'][] = [[get_class($this), 'processBatch'], [$config_importer, $sync_step]];
|
||||
$batch['operations'][] = [[ConfigImporterBatch::class, 'process'], [$config_importer, $sync_step]];
|
||||
}
|
||||
|
||||
batch_set($batch);
|
||||
|
@ -368,20 +369,15 @@ class ConfigSync extends FormBase {
|
|||
* The synchronization step to do.
|
||||
* @param array $context
|
||||
* The batch context.
|
||||
*
|
||||
* @deprecated in Drupal 8.6.0 and will be removed before 9.0.0. Use
|
||||
* \Drupal\Core\Config\Importer\ConfigImporterBatch::process() instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2897299
|
||||
*/
|
||||
public static function processBatch(ConfigImporter $config_importer, $sync_step, &$context) {
|
||||
if (!isset($context['sandbox']['config_importer'])) {
|
||||
$context['sandbox']['config_importer'] = $config_importer;
|
||||
}
|
||||
|
||||
$config_importer = $context['sandbox']['config_importer'];
|
||||
$config_importer->doSyncStep($sync_step, $context);
|
||||
if ($errors = $config_importer->getErrors()) {
|
||||
if (!isset($context['results']['errors'])) {
|
||||
$context['results']['errors'] = [];
|
||||
}
|
||||
$context['results']['errors'] = array_merge($context['results']['errors'], $errors);
|
||||
}
|
||||
@trigger_error('\Drupal\config\Form\ConfigSync::processBatch() deprecated in Drupal 8.6.0 and will be removed before 9.0.0. Use \Drupal\Core\Config\Importer\ConfigImporterBatch::process() instead. See https://www.drupal.org/node/2897299');
|
||||
ConfigImporterBatch::process($config_importer, $sync_step, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -389,27 +385,15 @@ class ConfigSync extends FormBase {
|
|||
*
|
||||
* This function is a static function to avoid serializing the ConfigSync
|
||||
* object unnecessarily.
|
||||
*
|
||||
* @deprecated in Drupal 8.6.0 and will be removed before 9.0.0. Use
|
||||
* \Drupal\Core\Config\Importer\ConfigImporterBatch::finish() instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2897299
|
||||
*/
|
||||
public static function finishBatch($success, $results, $operations) {
|
||||
if ($success) {
|
||||
if (!empty($results['errors'])) {
|
||||
foreach ($results['errors'] as $error) {
|
||||
\Drupal::messenger()->addError($error);
|
||||
\Drupal::logger('config_sync')->error($error);
|
||||
}
|
||||
\Drupal::messenger()->addWarning(\Drupal::translation()->translate('The configuration was imported with errors.'));
|
||||
}
|
||||
else {
|
||||
\Drupal::messenger()->addStatus(\Drupal::translation()->translate('The configuration was imported successfully.'));
|
||||
}
|
||||
}
|
||||
else {
|
||||
// An error occurred.
|
||||
// $operations contains the operations that remained unprocessed.
|
||||
$error_operation = reset($operations);
|
||||
$message = \Drupal::translation()->translate('An error occurred while processing %error_operation with arguments: @arguments', ['%error_operation' => $error_operation[0], '@arguments' => print_r($error_operation[1], TRUE)]);
|
||||
\Drupal::messenger()->addError($message);
|
||||
}
|
||||
@trigger_error('\Drupal\config\Form\ConfigSync::finishBatch() deprecated in Drupal 8.6.0 and will be removed before 9.0.0. Use \Drupal\Core\Config\Importer\ConfigImporterBatch::finish() instead. See https://www.drupal.org/node/2897299');
|
||||
ConfigImporterBatch::finish($success, $results, $operations);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ class ConfigUninstallViaCliImportTest extends KernelTestBase {
|
|||
$this->markTestSkipped('This test has to be run from the CLI');
|
||||
}
|
||||
|
||||
$this->installConfig(['system']);
|
||||
$this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.sync'));
|
||||
|
||||
// Set up the ConfigImporter object for testing.
|
||||
|
|
|
@ -59,7 +59,7 @@ class ContentModerationWorkflowConfigTest extends KernelTestBase {
|
|||
$this->installEntitySchema('node');
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('content_moderation_state');
|
||||
$this->installConfig('content_moderation');
|
||||
$this->installConfig(['system', 'content_moderation']);
|
||||
|
||||
NodeType::create([
|
||||
'type' => 'example',
|
||||
|
|
|
@ -33,6 +33,7 @@ class ContentTranslationConfigImportTest extends KernelTestBase {
|
|||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->installConfig(['system']);
|
||||
$this->installEntitySchema('entity_test_mul');
|
||||
$this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.sync'));
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ class NodeImportChangeTest extends KernelTestBase {
|
|||
parent::setUp();
|
||||
|
||||
// Set default storage backend.
|
||||
$this->installConfig(['field', 'node_test_config']);
|
||||
$this->installConfig(['system', 'field', 'node_test_config']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -28,7 +28,7 @@ class NodeImportCreateTest extends KernelTestBase {
|
|||
$this->installEntitySchema('user');
|
||||
|
||||
// Set default storage backend.
|
||||
$this->installConfig(['field']);
|
||||
$this->installConfig(['system', 'field']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -72,6 +72,9 @@ class SystemConfigSubscriber implements EventSubscriberInterface {
|
|||
* The config import event.
|
||||
*/
|
||||
public function onConfigImporterValidateSiteUUID(ConfigImporterEvent $event) {
|
||||
if (!$event->getConfigImporter()->getStorageComparer()->getSourceStorage()->exists('system.site')) {
|
||||
$event->getConfigImporter()->logError($this->t('This import does not contain system.site configuration, so has been rejected.'));
|
||||
}
|
||||
if (!$event->getConfigImporter()->getStorageComparer()->validateSiteUuid()) {
|
||||
$event->getConfigImporter()->logError($this->t('Site UUID in source storage does not match the target storage.'));
|
||||
}
|
||||
|
|
|
@ -1002,6 +1002,19 @@ function system_requirements($phase) {
|
|||
];
|
||||
}
|
||||
|
||||
// During installs from configuration don't support install profiles that
|
||||
// implement hook_install.
|
||||
if ($phase == 'install' && !empty($install_state['config_install_path'])) {
|
||||
$install_hook = $install_state['parameters']['profile'] . '_install';
|
||||
if (function_exists($install_hook)) {
|
||||
$requirements['config_install'] = [
|
||||
'title' => t('Configuration install'),
|
||||
'value' => $install_state['parameters']['profile'],
|
||||
'description' => t('The selected profile has a hook_install() implementation and therefore can not be installed from configuration.'),
|
||||
'severity' => REQUIREMENT_ERROR,
|
||||
];
|
||||
}
|
||||
}
|
||||
return $requirements;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\FunctionalTests\Installer;
|
||||
|
||||
/**
|
||||
* Verifies that installing from existing configuration works.
|
||||
*
|
||||
* @group Installer
|
||||
*/
|
||||
class InstallerExistingConfigMultilingualTest extends InstallerExistingConfigTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $profile = 'testing_config_install_multilingual';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getConfigTarball() {
|
||||
return __DIR__ . '/../../../fixtures/config_install/multilingual.tar.gz';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\FunctionalTests\Installer;
|
||||
|
||||
/**
|
||||
* Verifies that profiles invalid config can not be installed.
|
||||
*
|
||||
* @group Installer
|
||||
*/
|
||||
class InstallerExistingConfigNoConfigTest extends InstallerExistingConfigTestBase {
|
||||
|
||||
protected $profile = 'no_config_profile';
|
||||
|
||||
/**
|
||||
* Final installer step: Configure site.
|
||||
*/
|
||||
protected function setUpSite() {
|
||||
// There are errors therefore there is nothing to do here.
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getConfigTarball() {
|
||||
return __DIR__ . '/../../../fixtures/config_install/testing_config_install_no_config.tar.gz';
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that profiles with an empty config/sync directory do not work.
|
||||
*/
|
||||
public function testConfigSync() {
|
||||
$this->assertTitle('Configuration validation | Drupal');
|
||||
$this->assertText('The configuration synchronization failed validation.');
|
||||
$this->assertText('This import is empty and if applied would delete all of your configuration, so has been rejected.');
|
||||
|
||||
// Ensure there is no continuation button.
|
||||
$this->assertNoText('Save and continue');
|
||||
$this->assertNoFieldById('edit-submit');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\FunctionalTests\Installer;
|
||||
|
||||
/**
|
||||
* Testing installing from config without system.site.
|
||||
*
|
||||
* @group Installer
|
||||
*/
|
||||
class InstallerExistingConfigNoSystemSiteTest extends InstallerExistingConfigTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function prepareEnvironment() {
|
||||
parent::prepareEnvironment();
|
||||
// File API functions are not available yet.
|
||||
unlink($this->siteDirectory . '/profiles/' . $this->profile . '/config/sync/system.site.yml');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUpSite() {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that profiles with no system.site do not work.
|
||||
*/
|
||||
public function testConfigSync() {
|
||||
$this->htmlOutput(NULL);
|
||||
$this->assertTitle('Configuration validation | Drupal');
|
||||
$this->assertText('The configuration synchronization failed validation.');
|
||||
$this->assertText('This import does not contain system.site configuration, so has been rejected.');
|
||||
|
||||
// Ensure there is no continuation button.
|
||||
$this->assertNoText('Save and continue');
|
||||
$this->assertNoFieldById('edit-submit');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getConfigTarball() {
|
||||
return __DIR__ . '/../../../fixtures/config_install/testing_config_install.tar.gz';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\FunctionalTests\Installer;
|
||||
|
||||
/**
|
||||
* Verifies that profiles with hook_install() can't be installed from config.
|
||||
*
|
||||
* @group Installer
|
||||
*/
|
||||
class InstallerExistingConfigProfileHookInstall extends InstallerExistingConfigTestBase {
|
||||
|
||||
protected $profile = 'config_profile_with_hook_install';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function visitInstaller() {
|
||||
// Create an .install file with a hook_install() implementation.
|
||||
$path = $this->siteDirectory . '/profiles/' . $this->profile;
|
||||
$contents = <<<EOF
|
||||
<?php
|
||||
|
||||
function config_profile_with_hook_install_install() {
|
||||
}
|
||||
EOF;
|
||||
file_put_contents("$path/{$this->profile}.install", $contents);
|
||||
parent::visitInstaller();
|
||||
}
|
||||
|
||||
/**
|
||||
* Installer step: Configure settings.
|
||||
*/
|
||||
protected function setUpSettings() {
|
||||
// There are errors therefore there is nothing to do here.
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Final installer step: Configure site.
|
||||
*/
|
||||
protected function setUpSite() {
|
||||
// There are errors therefore there is nothing to do here.
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getConfigTarball() {
|
||||
// We're not going to get to the config import stage so this does not
|
||||
// matter.
|
||||
return __DIR__ . '/../../../fixtures/config_install/testing_config_install_no_config.tar.gz';
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms the installation has failed and the expected error is displayed.
|
||||
*/
|
||||
public function testConfigSync() {
|
||||
$this->assertTitle('Requirements problem | Drupal');
|
||||
$this->assertText($this->profile);
|
||||
$this->assertText('The selected profile has a hook_install() implementation and therefore can not be installed from configuration.');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\FunctionalTests\Installer;
|
||||
|
||||
/**
|
||||
* Verifies that installing from existing configuration works.
|
||||
*
|
||||
* @group Installer
|
||||
*/
|
||||
class InstallerExistingConfigTest extends InstallerExistingConfigTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUpSite() {
|
||||
// The configuration is from a site installed in French.
|
||||
// So after selecting the profile the installer detects that the site must
|
||||
// be installed in French, thus we change the button translation.
|
||||
$this->translations['Save and continue'] = 'Enregistrer et continuer';
|
||||
parent::setUpSite();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getConfigTarball() {
|
||||
return __DIR__ . '/../../../fixtures/config_install/testing_config_install.tar.gz';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\FunctionalTests\Installer;
|
||||
|
||||
use Drupal\Component\Serialization\Yaml;
|
||||
use Drupal\Core\Archiver\ArchiveTar;
|
||||
|
||||
/**
|
||||
* Provides a base class for testing installing from existing configuration.
|
||||
*/
|
||||
abstract class InstallerExistingConfigTestBase extends InstallerTestBase {
|
||||
|
||||
/**
|
||||
* This is set by the profile in the core.extension extracted.
|
||||
*/
|
||||
protected $profile = NULL;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function prepareEnvironment() {
|
||||
parent::prepareEnvironment();
|
||||
$archiver = new ArchiveTar($this->getConfigTarball(), 'gz');
|
||||
|
||||
if ($this->profile === NULL) {
|
||||
$core_extension = Yaml::decode($archiver->extractInString('core.extension.yml'));
|
||||
$this->profile = $core_extension['profile'];
|
||||
}
|
||||
|
||||
// Create a profile for testing.
|
||||
$info = [
|
||||
'type' => 'profile',
|
||||
'core' => \Drupal::CORE_COMPATIBILITY,
|
||||
'name' => 'Configuration installation test profile (' . $this->profile . ')',
|
||||
];
|
||||
// File API functions are not available yet.
|
||||
$path = $this->siteDirectory . '/profiles/' . $this->profile;
|
||||
|
||||
mkdir($path, 0777, TRUE);
|
||||
file_put_contents("$path/{$this->profile}.info.yml", Yaml::encode($info));
|
||||
|
||||
// Create config/sync directory and extract tarball contents to it.
|
||||
$config_sync_directory = $path . '/config/sync';
|
||||
mkdir($config_sync_directory, 0777, TRUE);
|
||||
$files = [];
|
||||
$list = $archiver->listContent();
|
||||
if (is_array($list)) {
|
||||
/** @var array $list */
|
||||
foreach ($list as $file) {
|
||||
$files[] = $file['filename'];
|
||||
}
|
||||
$archiver->extractList($files, $config_sync_directory);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the filepath to the configuration tarball.
|
||||
*
|
||||
* The tarball will be extracted to the install profile's config/sync
|
||||
* directory for testing.
|
||||
*
|
||||
* @return string
|
||||
* The filepath to the configuration tarball.
|
||||
*/
|
||||
abstract protected function getConfigTarball();
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function installParameters() {
|
||||
$parameters = parent::installParameters();
|
||||
|
||||
// The options that change configuration are disabled when installing from
|
||||
// existing configuration.
|
||||
unset($parameters['forms']['install_configure_form']['site_name']);
|
||||
unset($parameters['forms']['install_configure_form']['site_mail']);
|
||||
unset($parameters['forms']['install_configure_form']['update_status_module']);
|
||||
|
||||
return $parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that the installation installed the configuration correctly.
|
||||
*/
|
||||
public function testConfigSync() {
|
||||
// After installation there is no snapshot and nothing to import.
|
||||
$change_list = $this->configImporter()->getStorageComparer()->getChangelist();
|
||||
$expected = [
|
||||
'create' => [],
|
||||
// The system.mail is changed configuration because the test system
|
||||
// changes it to ensure that mails are not sent.
|
||||
'update' => ['system.mail'],
|
||||
'delete' => [],
|
||||
'rename' => [],
|
||||
];
|
||||
$this->assertEqual($expected, $change_list);
|
||||
}
|
||||
|
||||
}
|
|
@ -32,7 +32,7 @@ class ConfigImportRecreateTest extends KernelTestBase {
|
|||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('node');
|
||||
$this->installConfig(['field', 'node']);
|
||||
$this->installConfig(['system', 'field', 'node']);
|
||||
|
||||
$this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.sync'));
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ class ConfigImportRenameValidationTest extends KernelTestBase {
|
|||
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('node');
|
||||
$this->installConfig(['field']);
|
||||
$this->installConfig(['system', 'field']);
|
||||
|
||||
// Set up the ConfigImporter object for testing.
|
||||
$storage_comparer = new StorageComparer(
|
||||
|
|
|
@ -33,7 +33,7 @@ class ConfigImporterMissingContentTest extends KernelTestBase {
|
|||
$this->installSchema('system', 'sequences');
|
||||
$this->installEntitySchema('entity_test');
|
||||
$this->installEntitySchema('user');
|
||||
$this->installConfig(['config_test']);
|
||||
$this->installConfig(['system', 'config_test']);
|
||||
// Installing config_test's default configuration pollutes the global
|
||||
// variable being used for recording hook invocations by this test already,
|
||||
// so it has to be cleared out manually.
|
||||
|
|
|
@ -33,7 +33,7 @@ class ConfigImporterTest extends KernelTestBase {
|
|||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->installConfig(['config_test']);
|
||||
$this->installConfig(['system', 'config_test']);
|
||||
// Installing config_test's default configuration pollutes the global
|
||||
// variable being used for recording hook invocations by this test already,
|
||||
// so it has to be cleared out manually.
|
||||
|
|
|
@ -20,6 +20,7 @@ class ConfigOverrideTest extends KernelTestBase {
|
|||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installConfig(['system']);
|
||||
$this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.sync'));
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ class ConfigSnapshotTest extends KernelTestBase {
|
|||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installConfig(['system']);
|
||||
// Update the config snapshot. This allows the parent::setUp() to write
|
||||
// configuration files.
|
||||
\Drupal::service('config.manager')->createSnapshot(\Drupal::service('config.storage'), \Drupal::service('config.storage.snapshot'));
|
||||
|
|
|
@ -43,6 +43,7 @@ class ContentEntityNullStorageTest extends KernelTestBase {
|
|||
* @see \Drupal\Core\Entity\Event\BundleConfigImportValidate
|
||||
*/
|
||||
public function testDeleteThroughImport() {
|
||||
$this->installConfig(['system']);
|
||||
$contact_form = ContactForm::create(['id' => 'test']);
|
||||
$contact_form->save();
|
||||
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue