- <?php
- * @file
- * Admin page callbacks for the Configuration Management module.
- */
-
- * Form callback; Build the form for syncing all staged configuration.
- */
- function config_sync_form(array $form, array &$form_state) {
- $form_state['config_statuses'] = config_get_statuses();
- $changed_config_count = count(array_filter($form_state['config_statuses']));
- $modified_times = config_get_modified_times();
- $config_file_errors = array();
-
-
- $form['#attached']['library'][] = array('system', 'backdrop.ajax');
- $form['#attached']['css'][] = backdrop_get_path('module', 'config') . '/css/config.admin.css';
-
- $form['description'] = array(
- '#type' => 'help',
- '#markup' => t('This form allows you to synchronize configuration between sites, such as in development, staging, and production servers. This page shows you the current differences between your current configuration and changes that have been staged for deployment.') . '</p></p>' . t('When the configuration files are in version control, it may be preferable to not empty the staging directory after each sync. The <code>config_sync_clear_staging</code> setting can be disabled in <code>settings.php</code>. Read more about <a href="!url" target="_blank">managing configuration</a>.', array('!url' => url('https://docs.backdropcms.org/documentation/working-with-configuration'))),
- );
-
- if ($changed_config_count === 0) {
- $form['no_changes'] = array(
- '#theme' => 'table',
- '#header' => array('Name', 'Operations'),
- '#rows' => array(),
- '#empty' => t('There are no configuration changes currently staged. You may <a href="!export">export this site\'s configuration</a> or <a href="!import">import to stage new changes</a>.', array('!export' => url('admin/config/development/configuration/full/export'), '!import' => url('admin/config/development/configuration/full/import'))),
- );
- $form['actions']['#access'] = FALSE;
- return $form;
- }
-
- foreach ($form_state['config_statuses'] as $config_file => $config_change_type) {
- if ($config_change_type === 'error') {
- $config_file_errors[] = $config_file;
- }
- elseif (!is_null($config_change_type)) {
- $links['view_diff'] = array(
- '#type' => 'link',
- '#title' => t('View differences'),
- '#href' => 'admin/config/development/configuration/sync/diff/' . $config_file,
- '#attributes' => array(
- 'class' => array('use-ajax'),
- 'data-dialog' => 'true',
- 'data-dialog-options' => '{"dialogClass": "config-diff"}',
- ),
- );
- $row = array(
- 'name' => $config_file,
- );
- if ($config_change_type !== 'delete') {
- $row['date'] = isset($modified_times[$config_file]) ? format_date($modified_times[$config_file], 'short') : '';
- }
- $row['operations'] = array(
- 'data' => $links,
- );
- $form[$config_change_type]['list']['#rows'][] = $row;
- }
- }
-
- foreach (array('create', 'delete', 'update') as $config_change_type) {
- if (!isset($form[$config_change_type])) {
- continue;
- }
-
- switch ($config_change_type) {
- case 'create':
- $heading = format_plural(count($form[$config_change_type]['list']['#rows']), '@count new configuration', '@count new configuration');
- break;
-
- case 'update':
- $heading = format_plural(count($form[$config_change_type]['list']['#rows']), '@count configuration changed', '@count configurations changed');
- break;
-
- case 'delete':
- $heading = format_plural(count($form[$config_change_type]['list']['#rows']), '@count configuration removed', '@count configurations removed');
- break;
- }
-
- $form[$config_change_type]['heading'] = array(
- '#markup' => '<h3>' . $heading . '</h3>',
- '#weight' => -1,
- );
- $header = ($config_change_type === 'delete') ? array(t('Name'), t('Operations')) : array(t('Name'), t('Modified time'), t('Operations'));
- $form[$config_change_type]['list'] += array(
- '#theme' => 'table',
- '#header' => $header,
- );
- }
-
- if ($config_file_errors) {
- backdrop_set_message(t('The following configuration files could not be parsed, synchronization cannot proceed.') . theme('item_list', array('items' => $config_file_errors)), 'error');
- }
-
- $form['actions'] = array(
- '#type' => 'actions'
- );
- $form['actions']['submit'] = array(
- '#type' => 'submit',
- '#value' => t('Import all'),
- '#access' => $changed_config_count > 0 && empty($config_file_errors),
- );
-
- return $form;
- }
-
- * Submit handler for config_sync_form().
- */
- function config_sync_form_submit(array &$form, array &$form_state) {
- $last_sync_time = state_get('config_sync');
- if ($last_sync_time == NULL || $last_sync_time < REQUEST_TIME - 120) {
- module_load_include('inc', 'config', 'config.sync');
- $config_sync_batch = config_sync_batch($form_state['config_statuses']);
- batch_set($config_sync_batch);
- }
- else {
- backdrop_set_message(t('Another request may be synchronizing configuration already or a sync failed unexpectedly. Please wait a minute and try again.'), 'error');
- }
-
- return $form;
- }
-
- * Shows diff of specified configuration file.
- *
- * @param string $config_file
- * The name of the configuration file.
- *
- * @return string
- * Table showing a two-way diff between the active and staged configuration.
- */
- function config_diff_page($config_file) {
- $diff = config_diff($config_file);
-
- $build = array();
-
- $build['#title'] = t('View changes of @config_file', array('@config_file' => $config_file));
-
- $build['#attached']['css'][] = backdrop_get_path('module', 'system') . '/css/system.diff.css';
-
- $build['diff'] = array(
- '#theme' => 'table',
- '#header' => array(
- array('data' => t('Old'), 'colspan' => '2', 'class' => array('config-old')),
- array('data' => t('New'), 'colspan' => '2', 'class' => array('config-new')),
- ),
- '#attributes' => array('class' => array('diff-table')),
- '#rows' => $diff,
- '#sticky' => FALSE,
- );
-
- $build['actions'] = array(
- '#type' => 'actions',
- '#attributes' => array('class' => array('form-actions')),
- );
- $build['actions']['back'] = array(
- '#type' => 'link',
- '#title' => t('Back to "Synchronize configuration" page.'),
- '#href' => 'admin/config/development/configuration',
- '#attributes' => array('class' => array('button', 'button-secondary', 'form-submit')),
- );
-
- if (backdrop_is_dialog()) {
- $build['actions']['back']['#attributes']['class'][] = 'dialog-cancel';
- $build['actions']['back']['#title'] = t('Close');
- }
-
- return $build;
- }
-
- * Form callback; Provide a form for exporting the current configuration.
- */
- function config_export_full_form(array $form, array $form_state) {
- $form['description'] = array(
- '#type' => 'help',
- '#markup' => t('This form may be used to generate a full export of your entire site\'s configuration. This configuration file should remain compressed when you upload it to the destination site.'),
- );
- $form['actions'] = array('#type' => 'actions');
- $form['actions']['submit'] = array(
- '#type' => 'submit',
- '#value' => t('Export'),
- );
- return $form;
- }
-
- * Submit handler for config_export_full_form().
- */
- function config_export_full_form_submit(array $form, array &$form_state) {
- $form_state['redirect'] = 'admin/config/development/configuration/full/export-download';
- }
-
- * Downloads a tarball of the site configuration.
- */
- function config_download_full_export() {
- $file_path = file_create_filename('config.tar.gz', file_directory_temp());
- $config_storage = config_get_config_storage();
- $config_storage->exportArchive($file_path);
- $filename = str_replace(file_directory_temp() . '/', '', $file_path);
- $headers = array(
- 'Content-Disposition' => 'attachment; filename=config.tar.gz',
- 'Content-type' => 'application/x-gzip',
- );
- file_transfer('temporary://' . $filename, $headers);
- file_unmanaged_delete($file_path);
- }
-
- * Form callback; Provide a form for importing a full set of configuration.
- */
- function config_import_full_form(array $form, array &$form_state) {
- $form['description'] = array(
- '#type' => 'help',
- '#markup' => t('This form imports a site configuration archive from another site. Upload an export file below to stage the changes (you\'ll have a chance to review and confirm the changes first). To generate an export file, visit the <a href="!export">full export</a> page.', array('!export' => url('admin/config/development/configuration/full/export'))),
- );
- $form['import_tarball'] = array(
- '#type' => 'managed_file',
- '#title' => t('Select your configuration export archive'),
- '#description' => t('This file should be a compressed archive with the extension "tar.gz".'),
- '#upload_validators' => array(
- 'file_validate_extensions' => array('tar gz'),
- ),
- '#progress_indicator' => 'bar',
- '#upload_location' => 'temporary://',
- );
-
- $form['actions']['#type'] = 'actions';
- $form['actions']['submit'] = array(
- '#type' => 'submit',
- '#value' => t('Stage import'),
- );
- return $form;
- }
-
- * Submit handler for config_import_full_form().
- */
- function config_import_full_form_submit(array &$form, array &$form_state) {
- if ($fid = $form_state['values']['import_tarball']) {
-
- $config_storage = config_get_config_storage('staging');
- $config_storage->deleteAll();
-
-
- $file = file_load($fid);
- try {
- $config_storage->importArchive($file->uri);
- }
- catch (\ConfigStorageException $e) {
- form_set_error('import_tarball', t('Could not extract the contents of the tar file. The error message is "@message".', array('@message' => $e->getMessage())));
- }
- file_delete($file->fid);
-
-
- $config_statuses = array_filter(config_get_statuses());
- if (empty($config_statuses)) {
- $config_storage->deleteAll();
- backdrop_set_message(t('The uploaded configuration matches the current site configuration exactly. No changes were made.'), 'warning');
- }
- else {
- backdrop_set_message(t('Your configuration files were successfully uploaded. You may view the differences below. Import into your site by using the "Import all" button.'));
- $form_state['redirect'] = 'admin/config/development/configuration';
- }
- }
- }
-
- * Form callback; Builds the form for exporting a single configuration file.
- */
- function config_export_single_form(array $form, array &$form_state, $config_group = NULL, $config_name = NULL) {
- $form_state['config_groups'] = config_get_prefix_groups();
- $form['#attached']['css'][] = backdrop_get_path('module', 'config') . '/css/config.admin.css';
-
- $form['description'] = array(
- '#type' => 'help',
- '#markup' => t('This form may be used to export a single configuration so that it may be imported on a different site. Select a configuration group and name to generate configuration for copy/paste.'),
- );
-
-
- if (empty($config_group) && isset($_GET['group'])) {
- $config_group = $_GET['group'];
- }
-
- $group_options = backdrop_map_assoc(array_keys($form_state['config_groups']));
- natsort($group_options);
- $form['config_group'] = array(
- '#title' => t('Configuration group'),
- '#type' => 'select',
- '#options' => $group_options,
- '#default_value' => $config_group,
- '#required' => TRUE,
- '#ajax' => array(
-
- 'callback' => 'config_export_single_form_update_type',
- ),
- );
-
-
- if (empty($config_name) && isset($_GET['name'])) {
- $config_name = $_GET['name'];
- }
-
- $default_type = isset($form_state['values']['config_group']) ? $form_state['values']['config_group'] : $config_group;
- $config_names = array();
- if ($default_type) {
-
- if (isset($form_state['config_groups'][$default_type])) {
- $config_names = $form_state['config_groups'][$default_type];
- natsort($config_names);
- }
- else {
- $config_names = array();
- backdrop_set_message(t('Configuration group %type not available.', array(
- '%type' => $default_type,
- )), 'warning');
- }
- }
- $form['config_name'] = array(
- '#title' => t('Configuration name'),
- '#type' => 'select',
- '#options' => $config_names,
- '#default_value' => $config_name,
- '#required' => TRUE,
- '#prefix' => '<div id="edit-config-type-wrapper">',
- '#suffix' => '</div>',
- '#ajax' => array(
- 'callback' => 'config_export_single_form_update_export',
- 'wrapper' => 'edit-export-wrapper',
- ),
- );
-
- $form['export'] = array(
- '#title' => t('Exported configuration'),
- '#type' => 'textarea',
- '#rows' => 12,
- '#prefix' => '<div id="edit-export-wrapper">',
- '#suffix' => '</div>',
- );
-
- if ($config_group || $config_name) {
- if (empty($form_state['values'])) {
- $form_state['values'] = array();
- }
- $form_state['values'] += array(
- 'config_group' => $config_group,
- 'config_name' => $config_name,
- );
- $form['export'] = config_export_single_form_update_export($form, $form_state);
- }
-
- return $form;
- }
-
- * Handles switching the configuration type selector.
- */
- function config_export_single_form_update_type($form, &$form_state) {
-
- $form['export']['#value'] = '';
- $form['export']['#rows'] = 12;
-
-
-
- $commands = array();
- $commands[] = ajax_command_replace('#edit-config-type-wrapper', backdrop_render($form['config_name']));
- $commands[] = ajax_command_replace('#edit-export-wrapper', backdrop_render($form['export']));
- $commands[] = ajax_command_prepend(NULL, theme('status_messages'));
- return array(
- '#type' => 'ajax',
- '#commands' => $commands,
- );
- }
-
- * AJAX submit handler for config_export_single_form().
- *
- * Handles switching the export textarea.
- */
- function config_export_single_form_update_export($form, &$form_state) {
- if ($form_state['values']['config_name']) {
- $config_name = $form_state['values']['config_name'];
- $storage = config_get_config_storage('active');
- $config = new Config($config_name, $storage);
-
-
- $data = array_merge(array('_config_name' => $config_name), $config->get());
- $value = $storage->encode($data);
- $row_count = substr_count($value, "\n");
- $form['export']['#value'] = $value;
- $form['export']['#rows'] = $row_count > 12 ? $row_count : 12;
- }
- else {
- $form['export']['#value'] = '';
- $form['export']['#rows'] = 12;
- }
- return $form['export'];
- }
-
- * Form callback; Build the form to import a single configuration file.
- */
- function config_import_single_form(array $form, array &$form_state) {
- form_load_include($form_state, 'inc', 'config', 'config.sync');
- $form['#attached']['css'][] = backdrop_get_path('module', 'config') . '/css/config.admin.css';
-
- $form['description'] = array(
- '#type' => 'help',
- '#markup' => t('This form imports a single configuration file from another site. To generate an export, visit the <a href="!export">single export</a> page.', array('!export' => url('admin/config/development/configuration/single/export'))),
- );
- $form['import'] = array(
- '#title' => t('Paste your configuration here'),
- '#type' => 'textarea',
- '#rows' => 12,
- '#required' => TRUE,
- );
- $form['actions'] = array('#type' => 'actions');
- $form['actions']['submit'] = array(
- '#type' => 'submit',
- '#value' => t('Import'),
- );
- return $form;
- }
-
- * Validate handler for config_import_single_form().
- */
- function config_import_single_form_validate(array &$form, array &$form_state) {
-
- $active_storage = config_get_config_storage('active');
- try {
- $data = $active_storage->decode($form_state['values']['import']);
- }
- catch (ConfigStorageException $e) {
- $data = FALSE;
- }
-
-
- $config_info = FALSE;
- if ($data === FALSE) {
- form_error($form['import'], t('The configuration provided could not be parsed.'));
- }
- elseif (empty($data['_config_name'])) {
- form_error($form['import'], t('The configuration could not be imported because the "_config_name" value is missing.'));
- }
- else {
- $config_name = $data['_config_name'];
- $config_info = config_get_info($config_name);
- if ($config_info === FALSE) {
- form_error($form['import'], t('The configuration could not be imported because no module could recognize the "@name" configuration name.', array('@name' => $config_name)));
- }
- }
-
-
-
- $config_change_type = NULL;
- if ($data && $config_info) {
- $active_config_exists = $active_storage->read($config_name) !== FALSE;
- if ($active_config_exists) {
- $config_change_type = 'update';
- }
- else {
- $config_change_type = 'create';
- }
- try {
- config_sync_validate_file($config_name, $config_change_type, NULL, $data);
- }
- catch (ConfigValidateException $e) {
- form_error($form['import'], $e->getMessage());
- }
- }
-
-
- $form_state['config_data'] = $data;
- $form_state['config_change_type'] = $config_change_type;
- }
-
- * Submit handler for config_import_single_form().
- */
- function config_import_single_form_submit(array &$form, array &$form_state) {
- $data = $form_state['config_data'];
- config_sync_file($data['_config_name'], $form_state['config_change_type'], $data);
- backdrop_flush_all_caches();
- backdrop_set_message(t('The %name configuration was imported successfully.', array('%name' => $data['_config_name'])));
- }
-
- * Utility function to get the current diffs between active and staging.
- */
- function config_get_statuses() {
- $active_storage = config_get_config_storage('active');
- $staging_storage = config_get_config_storage('staging');
-
-
- $active_files = $active_storage->listAll();
- $staging_files = $staging_storage->listAll();
- $all_files = array_unique(array_merge($active_files, $staging_files));
- $all_files = array_combine($all_files, array_fill(0, count($all_files), NULL));
- $config_statuses = $all_files;
-
- if ($staging_files) {
- if ($delete_diffs = array_diff($active_files, $staging_files)) {
- foreach ($delete_diffs as $filename) {
- $config_statuses[$filename] = 'delete';
- }
- }
- if ($create_diffs = array_diff($staging_files, $active_files)) {
- foreach ($create_diffs as $filename) {
- $config_statuses[$filename] = 'create';
- }
- }
- if ($remaining_diffs = array_diff($active_files, $delete_diffs)) {
- foreach ($remaining_diffs as $filename) {
- try {
- $active_config = $active_storage->read($filename);
- $staging_config = $staging_storage->read($filename);
- if ($active_config !== $staging_config) {
- $config_statuses[$filename] = 'update';
- }
- }
- catch (ConfigStorageReadException $e) {
- $config_statuses[$filename] = 'error';
- }
- }
- }
- }
- ksort($config_statuses);
-
- return $config_statuses;
- }
-
- * Utility function to get the current diffs between active and staging.
- */
- function config_get_modified_times() {
- $staging_storage = config_get_config_storage('staging');
- $staging_files = $staging_storage->listAll();
- $modified_times = array();
- foreach ($staging_files as $file) {
- $modified_times[$file] = $staging_storage->getModifiedTime($file);
- }
- return $modified_times;
- }
-
- * Return a formatted diff of a named config between staging and active.
- *
- * @param string $name
- * The name of the configuration object to diff.
- *
- * @return array
- * An array of formatted strings showing the diffs between the two storages.
- *
- * @see config_diff_page()
- */
- function config_diff($name) {
- $active_storage = config_get_config_storage('active');
- $staging_storage = config_get_config_storage('staging');
-
- $source_data = explode("\n", backdrop_json_encode($active_storage->read($name), TRUE));
- $target_data = explode("\n", backdrop_json_encode($staging_storage->read($name), TRUE));
-
-
- if ($source_data === array('false')) {
-
- $source_data = array(t('File added'));
- }
- if ($target_data === array('false')) {
-
- $target_data = array(t('File removed'));
- }
-
- $diff = new Diff($source_data, $target_data);
- $formatter = new BackdropDiffFormatter();
- return $formatter->format($diff);
- }