diff --git a/core/modules/config/config.admin.inc b/core/modules/config/config.admin.inc deleted file mode 100644 index 2d30e49..0000000 --- a/core/modules/config/config.admin.inc +++ /dev/null @@ -1,151 +0,0 @@ -listAll(); - $config_comparer = new StorageComparer(Drupal::service('config.storage.staging'), Drupal::service('config.storage')); - if (empty($source_list) || !$config_comparer->createChangelist()->hasChanges()) { - $form['no_changes'] = array( - '#markup' => t('There are no configuration changes.'), - ); - $form['actions']['#access'] = FALSE; - return $form; - } - else { - // Store the comparer for use in the submit. - $form_state['storage_comparer'] = $config_comparer; - } - - // Add the AJAX library to the form for dialog support. - $form['#attached']['library'][] = array('system', 'drupal.ajax'); - - foreach ($config_comparer->getChangelist() as $config_change_type => $config_files) { - if (empty($config_files)) { - continue; - } - - // @todo A table caption would be more appropriate, but does not have the - // visual importance of a heading. - $form[$config_change_type]['heading'] = array( - '#type' => 'html_tag', - '#tag' => 'h3', - ); - switch ($config_change_type) { - case 'create': - $form[$config_change_type]['heading']['#value'] = format_plural(count($config_files), '@count new', '@count new'); - break; - - case 'update': - $form[$config_change_type]['heading']['#value'] = format_plural(count($config_files), '@count changed', '@count changed'); - break; - - case 'delete': - $form[$config_change_type]['heading']['#value'] = format_plural(count($config_files), '@count removed', '@count removed'); - break; - } - $form[$config_change_type]['list'] = array( - '#theme' => 'table', - '#header' => array('Name', 'Operations'), - ); - - foreach ($config_files as $config_file) { - $links['view_diff'] = array( - 'title' => t('View differences'), - 'href' => 'admin/config/development/sync/diff/' . $config_file, - 'attributes' => array( - 'class' => array('use-ajax'), - 'data-accepts' => 'application/vnd.drupal-modal', - 'data-dialog-options' => json_encode(array( - 'width' => 700 - )), - ), - ); - $form[$config_change_type]['list']['#rows'][] = array( - 'name' => $config_file, - 'operations' => array( - 'data' => array( - '#type' => 'operations', - '#links' => $links, - ), - ), - ); - } - } -} - -/** - * Form constructor for configuration import form. - * - * @see config_admin_import_form_submit() - * @see config_import() - */ -function config_admin_import_form($form, &$form_state) { - // Retrieve a list of differences between last known state and active store. - $source_storage = Drupal::service('config.storage.staging'); - $target_storage = Drupal::service('config.storage'); - - $form['actions'] = array('#type' => 'actions'); - $form['actions']['submit'] = array( - '#type' => 'submit', - '#value' => t('Import all'), - ); - - config_admin_sync_form($form, $form_state, $source_storage, $target_storage); - - return $form; -} - -/** - * Form submission handler for config_admin_import_form(). - */ -function config_admin_import_form_submit($form, &$form_state) { - $config_importer = new ConfigImporter( - $form_state['storage_comparer'], - Drupal::service('event_dispatcher'), - Drupal::service('config.factory'), - Drupal::entityManager(), - Drupal::lock() - ); - if ($config_importer->alreadyImporting()) { - drupal_set_message(t('Another request may be synchronizing configuration already.')); - } - else{ - try { - $config_importer->import(); - drupal_flush_all_caches(); - drupal_set_message(t('The configuration was imported successfully.')); - } - catch (ConfigException $e) { - // Return a negative result for UI purposes. We do not differentiate between - // an actual synchronization error and a failed lock, because concurrent - // synchronizations are an edge-case happening only when multiple developers - // or site builders attempt to do it without coordinating. - watchdog_exception('config_import', $e); - drupal_set_message(t('The import failed due to an error. Any errors have been logged.'), 'error'); - } - } -} diff --git a/core/modules/config/config.module b/core/modules/config/config.module index 3e24189..4ac7d7e 100644 --- a/core/modules/config/config.module +++ b/core/modules/config/config.module @@ -64,10 +64,7 @@ function config_menu() { $items['admin/config/development/sync'] = array( 'title' => 'Synchronize configuration', 'description' => 'Synchronize configuration changes.', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('config_admin_import_form'), - 'access arguments' => array('synchronize configuration'), - 'file' => 'config.admin.inc', + 'route_name' => 'config_admin_import', ); $items['admin/config/development/export'] = array( 'title' => 'Configuration export', diff --git a/core/modules/config/config.routing.yml b/core/modules/config/config.routing.yml index 53c860c..5bdd1af 100644 --- a/core/modules/config/config.routing.yml +++ b/core/modules/config/config.routing.yml @@ -4,21 +4,32 @@ config_diff: _content: '\Drupal\config\Controller\ConfigController::diff' requirements: _permission: 'synchronize configuration' + config_export_download: pattern: '/admin/config/development/export-download' defaults: _controller: '\Drupal\config\Controller\ConfigController::downloadExport' requirements: _permission: 'export configuration' + config_export: pattern: '/admin/config/development/export' defaults: _form: '\Drupal\config\Form\ConfigExportForm' requirements: _permission: 'export configuration' + config_import: pattern: '/admin/config/development/import' defaults: _form: '\Drupal\config\Form\ConfigImportForm' requirements: _permission: 'import configuration' + + +config_admin_import: + pattern: '/admin/config/development/sync' + defaults: + _form: '\Drupal\config\Form\ConfigAdminImportForm' + requirements: + _permission: 'synchronize configuration' diff --git a/core/modules/config/lib/Drupal/config/Form/ConfigAdminImportForm.php b/core/modules/config/lib/Drupal/config/Form/ConfigAdminImportForm.php new file mode 100644 index 0000000..af55f31 --- /dev/null +++ b/core/modules/config/lib/Drupal/config/Form/ConfigAdminImportForm.php @@ -0,0 +1,178 @@ +configImporter = $config_importer; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('config.importer') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormID() { + return 'config_admin_import'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, array &$form_state) { + $source_list = $this->configImporter->getSourceStorage()->listAll(); + if (empty($source_list)) { + $form['no_changes'] = array( + '#markup' => t('There is no configuration to import.'), + ); + return $form; + } + + if (!$this->configImporter->hasChanges()) { + $form['no_changes'] = array( + '#markup' => t('There are no configuration changes.'), + ); + return $form; + } + + // Add the AJAX library to the form for dialog support. + $form['#attached']['library'][] = array('system', 'drupal.ajax'); + + foreach ($this->configImporter->getChangelist() as $config_change_type => $config_files) { + if (empty($config_files)) { + continue; + } + + // @todo A table caption would be more appropriate, but does not have the + // visual importance of a heading. + $form[$config_change_type]['heading'] = array( + '#theme' => 'html_tag__h3', + '#tag' => 'h3', + ); + switch ($config_change_type) { + case 'create': + $form[$config_change_type]['heading']['#value'] = format_plural(count($config_files), '@count new', '@count new'); + break; + + case 'update': + $form[$config_change_type]['heading']['#value'] = format_plural(count($config_files), '@count changed', '@count changed'); + break; + + case 'delete': + $form[$config_change_type]['heading']['#value'] = format_plural(count($config_files), '@count removed', '@count removed'); + break; + } + $form[$config_change_type]['list'] = array( + '#theme' => 'table', + '#header' => array('Name', 'Operations'), + ); + + foreach ($config_files as $config_file) { + $links['view_diff'] = array( + 'title' => t('View differences'), + 'href' => 'admin/config/development/sync/diff/' . $config_file, + 'attributes' => array( + 'class' => array('use-ajax'), + 'data-accepts' => 'application/vnd.drupal-modal', + 'data-dialog-options' => json_encode(array( + 'width' => 700 + )), + ), + ); + $form[$config_change_type]['list']['#rows'][] = array( + 'name' => $config_file, + 'operations' => array( + 'data' => array( + '#type' => 'operations', + '#links' => $links, + ), + ), + ); + } + } + + $form['actions'] = array( + '#type' => 'actions', + ); + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => t('Import all'), + ); + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, array &$form_state) { + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, array &$form_state) { + if ($this->configImporter->alreadyImporting()) { + drupal_set_message(t('Another request may be synchronizing configuration already.')); + } + else{ + try { + $this->configImporter->import(); + drupal_flush_all_caches(); + drupal_set_message(t('The configuration was imported successfully.')); + + // Once a sync completes, we empty the staging directory. This prevents + // changes from being accidentally overwritten by stray files getting + // imported later. + $source_storage = $this->configImporter->getSourceStorage(); + foreach ($source_storage->listAll() as $name) { + $source_storage->delete($name); + } + } + catch (ConfigException $e) { + // Return a negative result for UI purposes. We do not differentiate between + // an actual synchronization error and a failed lock, because concurrent + // synchronizations are an edge-case happening only when multiple developers + // or site builders attempt to do it without coordinating. + watchdog_exception('config_import', $e); + drupal_set_message(t('The import failed due to an error. Any errors have been logged.'), 'error'); + } + } + } + +} diff --git a/core/modules/config/lib/Drupal/config/Form/ConfigImportForm.php b/core/modules/config/lib/Drupal/config/Form/ConfigImportForm.php index 30dc2d3..8ced878 100644 --- a/core/modules/config/lib/Drupal/config/Form/ConfigImportForm.php +++ b/core/modules/config/lib/Drupal/config/Form/ConfigImportForm.php @@ -1,5 +1,10 @@ '
' . t('Use the upload button below.') . '
', - ); - $form['import_tarball'] = array( - '#type' => 'file', - '#value' => t('Select your configuration export file'), - '#description' => t('This form will redirect you to the import configuration screen.'), - ); - $form['submit'] = array( + // Retrieve a list of differences between last known state and active store. + $source_storage = Drupal::service('config.storage.staging'); + $target_storage = Drupal::service('config.storage'); + + $form['actions'] = array('#type' => 'actions'); + $form['actions']['submit'] = array( '#type' => 'submit', - '#value' => t('Upload'), + '#value' => t('Import all'), ); + + $this->buildSyncForm($form, $form_state, $source_storage, $target_storage); + return $form; } @@ -37,18 +42,116 @@ public function validateForm(array &$form, array &$form_state) { } public function submitForm(array &$form, array &$form_state) { - if ($path = $form_state['values']['import_tarball']) { - \Drupal::service('config.storage.staging')->deleteAll(); - $archiver = new ArchiveTar($path, 'gz'); - $files = array(); - foreach ($archiver->listContent() as $file) { - $files[] = $file['filename']; + $config_importer = new ConfigImporter( + $form_state['storage_comparer'], + Drupal::service('event_dispatcher'), + Drupal::service('config.factory'), + Drupal::entityManager(), + Drupal::lock() + ); + if ($config_importer->alreadyImporting()) { + drupal_set_message(t('Another request may be synchronizing configuration already.')); + } + else { + try { + $config_importer->import(); + drupal_flush_all_caches(); + drupal_set_message(t('The configuration was imported successfully.')); + } + catch (ConfigException $e) { + // Return a negative result for UI purposes. We do not differentiate + // between an actual synchronization error and a failed lock, because + // concurrent synchronizations are an edge-case happening only when + // multiple developers or site builders attempt to do it without + // coordinating. + watchdog_exception('config_import', $e); + drupal_set_message(t('The import failed due to an error. Any errors have been logged.'), 'error'); } - $archiver->extractList($files, config_get_config_directory(CONFIG_STAGING_DIRECTORY)); - drupal_unlink($path); - drupal_set_message('Your configuration files were successfully uploaded, ready for import.'); - $form_state['redirect'] = 'admin/config/development/sync'; } } -} + /** + * Helper function to construct the storage changes in a configuration synchronization form. + * + * @param array $form + * The form structure to add to. Passed by reference. + * @param array $form_state + * The current state of the form. Passed by reference. + * @param \Drupal\Core\Config\StorageInterface $source_storage + * The source storage to retrieve differences from. + * + * @return array + * The form with the configuration storage changes. + */ + protected function buildSyncForm(array &$form, array &$form_state, StorageInterface $source_storage) { + $source_list = $source_storage->listAll(); + $config_comparer = new StorageComparer(Drupal::service('config.storage.staging'), Drupal::service('config.storage')); + if (empty($source_list) || !$config_comparer->createChangelist()->hasChanges()) { + $form['no_changes'] = array( + '#markup' => t('There are no configuration changes.'), + ); + $form['actions']['#access'] = FALSE; + return $form; + } + else { + // Store the comparer for use in the submit. + $form_state['storage_comparer'] = $config_comparer; + } + + // Add the AJAX library to the form for dialog support. + $form['#attached']['library'][] = array('system', 'drupal.ajax'); + + foreach ($config_comparer->getChangelist() as $config_change_type => $config_files) { + if (empty($config_files)) { + continue; + } + // @todo A table caption would be more appropriate, but does not have the + // visual importance of a heading. + $form[$config_change_type]['heading'] = array( + '#type' => 'html_tag', + '#tag' => 'h3', + ); + switch ($config_change_type) { + case 'create': + $form[$config_change_type]['heading']['#value'] = format_plural(count($config_files), '@count new', '@count new'); + break; + + case 'update': + $form[$config_change_type]['heading']['#value'] = format_plural(count($config_files), '@count changed', '@count changed'); + break; + + case 'delete': + $form[$config_change_type]['heading']['#value'] = format_plural(count($config_files), '@count removed', '@count removed'); + break; + } + $form[$config_change_type]['list'] = array( + '#theme' => 'table', + '#header' => array('Name', 'Operations'), + ); + + foreach ($config_files as $config_file) { + $links['view_diff'] = array( + 'title' => t('View differences'), + 'href' => 'admin/config/development/sync/diff/' . $config_file, + 'attributes' => array( + 'class' => array('use-ajax'), + 'data-accepts' => 'application/vnd.drupal-modal', + 'data-dialog-options' => json_encode(array( + 'width' => 700 + )), + ), + ); + $form[$config_change_type]['list']['#rows'][] = array( + 'name' => $config_file, + 'operations' => array( + 'data' => array( + '#type' => 'operations', + '#links' => $links, + ), + ), + ); + } + } + } + +}