PageRenderTime 38ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 1ms

/core/modules/config/src/Form/ConfigSync.php

https://gitlab.com/reasonat/test8
PHP | 414 lines | 261 code | 32 blank | 121 comment | 25 complexity | 35bf9eda303fc008d398cf297d5a4190 MD5 | raw file
  1. <?php
  2. namespace Drupal\config\Form;
  3. use Drupal\Core\Config\ConfigImporterException;
  4. use Drupal\Core\Config\ConfigImporter;
  5. use Drupal\Core\Config\TypedConfigManagerInterface;
  6. use Drupal\Core\Extension\ModuleHandlerInterface;
  7. use Drupal\Core\Extension\ModuleInstallerInterface;
  8. use Drupal\Core\Extension\ThemeHandlerInterface;
  9. use Drupal\Core\Config\ConfigManagerInterface;
  10. use Drupal\Core\Form\FormBase;
  11. use Drupal\Core\Config\StorageInterface;
  12. use Drupal\Core\Form\FormStateInterface;
  13. use Drupal\Core\Lock\LockBackendInterface;
  14. use Drupal\Core\Config\StorageComparer;
  15. use Drupal\Core\Render\RendererInterface;
  16. use Drupal\Core\Url;
  17. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  18. use Symfony\Component\DependencyInjection\ContainerInterface;
  19. /**
  20. * Construct the storage changes in a configuration synchronization form.
  21. */
  22. class ConfigSync extends FormBase {
  23. /**
  24. * The database lock object.
  25. *
  26. * @var \Drupal\Core\Lock\LockBackendInterface
  27. */
  28. protected $lock;
  29. /**
  30. * The sync configuration object.
  31. *
  32. * @var \Drupal\Core\Config\StorageInterface
  33. */
  34. protected $syncStorage;
  35. /**
  36. * The active configuration object.
  37. *
  38. * @var \Drupal\Core\Config\StorageInterface
  39. */
  40. protected $activeStorage;
  41. /**
  42. * The snapshot configuration object.
  43. *
  44. * @var \Drupal\Core\Config\StorageInterface
  45. */
  46. protected $snapshotStorage;
  47. /**
  48. * Event dispatcher.
  49. *
  50. * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
  51. */
  52. protected $eventDispatcher;
  53. /**
  54. * The configuration manager.
  55. *
  56. * @var \Drupal\Core\Config\ConfigManagerInterface;
  57. */
  58. protected $configManager;
  59. /**
  60. * The typed config manager.
  61. *
  62. * @var \Drupal\Core\Config\TypedConfigManagerInterface
  63. */
  64. protected $typedConfigManager;
  65. /**
  66. * The module handler.
  67. *
  68. * @var \Drupal\Core\Extension\ModuleHandlerInterface
  69. */
  70. protected $moduleHandler;
  71. /**
  72. * The theme handler.
  73. *
  74. * @var \Drupal\Core\Extension\ThemeHandlerInterface
  75. */
  76. protected $themeHandler;
  77. /**
  78. * The module installer.
  79. *
  80. * @var \Drupal\Core\Extension\ModuleInstallerInterface
  81. */
  82. protected $moduleInstaller;
  83. /**
  84. * The renderer.
  85. *
  86. * @var \Drupal\Core\Render\RendererInterface
  87. */
  88. protected $renderer;
  89. /**
  90. * Constructs the object.
  91. *
  92. * @param \Drupal\Core\Config\StorageInterface $sync_storage
  93. * The source storage.
  94. * @param \Drupal\Core\Config\StorageInterface $active_storage
  95. * The target storage.
  96. * @param \Drupal\Core\Config\StorageInterface $snapshot_storage
  97. * The snapshot storage.
  98. * @param \Drupal\Core\Lock\LockBackendInterface $lock
  99. * The lock object.
  100. * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
  101. * Event dispatcher.
  102. * @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
  103. * Configuration manager.
  104. * @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
  105. * The typed configuration manager.
  106. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
  107. * The module handler.
  108. * @param \Drupal\Core\Extension\ModuleInstallerInterface $module_installer
  109. * The module installer.
  110. * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
  111. * The theme handler.
  112. * @param \Drupal\Core\Render\RendererInterface
  113. * The renderer.
  114. */
  115. public function __construct(StorageInterface $sync_storage, StorageInterface $active_storage, StorageInterface $snapshot_storage, LockBackendInterface $lock, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, TypedConfigManagerInterface $typed_config, ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, ThemeHandlerInterface $theme_handler, RendererInterface $renderer) {
  116. $this->syncStorage = $sync_storage;
  117. $this->activeStorage = $active_storage;
  118. $this->snapshotStorage = $snapshot_storage;
  119. $this->lock = $lock;
  120. $this->eventDispatcher = $event_dispatcher;
  121. $this->configManager = $config_manager;
  122. $this->typedConfigManager = $typed_config;
  123. $this->moduleHandler = $module_handler;
  124. $this->moduleInstaller = $module_installer;
  125. $this->themeHandler = $theme_handler;
  126. $this->renderer = $renderer;
  127. }
  128. /**
  129. * {@inheritdoc}
  130. */
  131. public static function create(ContainerInterface $container) {
  132. return new static(
  133. $container->get('config.storage.sync'),
  134. $container->get('config.storage'),
  135. $container->get('config.storage.snapshot'),
  136. $container->get('lock.persistent'),
  137. $container->get('event_dispatcher'),
  138. $container->get('config.manager'),
  139. $container->get('config.typed'),
  140. $container->get('module_handler'),
  141. $container->get('module_installer'),
  142. $container->get('theme_handler'),
  143. $container->get('renderer')
  144. );
  145. }
  146. /**
  147. * {@inheritdoc}
  148. */
  149. public function getFormId() {
  150. return 'config_admin_import_form';
  151. }
  152. /**
  153. * {@inheritdoc}
  154. */
  155. public function buildForm(array $form, FormStateInterface $form_state) {
  156. $form['actions'] = array('#type' => 'actions');
  157. $form['actions']['submit'] = array(
  158. '#type' => 'submit',
  159. '#value' => $this->t('Import all'),
  160. );
  161. $source_list = $this->syncStorage->listAll();
  162. $storage_comparer = new StorageComparer($this->syncStorage, $this->activeStorage, $this->configManager);
  163. if (empty($source_list) || !$storage_comparer->createChangelist()->hasChanges()) {
  164. $form['no_changes'] = array(
  165. '#type' => 'table',
  166. '#header' => array($this->t('Name'), $this->t('Operations')),
  167. '#rows' => array(),
  168. '#empty' => $this->t('There are no configuration changes to import.'),
  169. );
  170. $form['actions']['#access'] = FALSE;
  171. return $form;
  172. }
  173. elseif (!$storage_comparer->validateSiteUuid()) {
  174. drupal_set_message($this->t('The staged configuration cannot be imported, because it originates from a different site than this site. You can only synchronize configuration between cloned instances of this site.'), 'error');
  175. $form['actions']['#access'] = FALSE;
  176. return $form;
  177. }
  178. // A list of changes will be displayed, so check if the user should be
  179. // warned of potential losses to configuration.
  180. if ($this->snapshotStorage->exists('core.extension')) {
  181. $snapshot_comparer = new StorageComparer($this->activeStorage, $this->snapshotStorage, $this->configManager);
  182. if (!$form_state->getUserInput() && $snapshot_comparer->createChangelist()->hasChanges()) {
  183. $change_list = array();
  184. foreach ($snapshot_comparer->getAllCollectionNames() as $collection) {
  185. foreach ($snapshot_comparer->getChangelist(NULL, $collection) as $config_names) {
  186. if (empty($config_names)) {
  187. continue;
  188. }
  189. foreach ($config_names as $config_name) {
  190. $change_list[] = $config_name;
  191. }
  192. }
  193. }
  194. sort($change_list);
  195. $message = [
  196. [
  197. '#markup' => $this->t('The following items in your active configuration have changes since the last import that may be lost on the next import.')
  198. ],
  199. [
  200. '#theme' => 'item_list',
  201. '#items' => $change_list,
  202. ]
  203. ];
  204. drupal_set_message($this->renderer->renderPlain($message), 'warning');
  205. }
  206. }
  207. // Store the comparer for use in the submit.
  208. $form_state->set('storage_comparer', $storage_comparer);
  209. // Add the AJAX library to the form for dialog support.
  210. $form['#attached']['library'][] = 'core/drupal.dialog.ajax';
  211. foreach ($storage_comparer->getAllCollectionNames() as $collection) {
  212. if ($collection != StorageInterface::DEFAULT_COLLECTION) {
  213. $form[$collection]['collection_heading'] = array(
  214. '#type' => 'html_tag',
  215. '#tag' => 'h2',
  216. '#value' => $this->t('@collection configuration collection', array('@collection' => $collection)),
  217. );
  218. }
  219. foreach ($storage_comparer->getChangelist(NULL, $collection) as $config_change_type => $config_names) {
  220. if (empty($config_names)) {
  221. continue;
  222. }
  223. // @todo A table caption would be more appropriate, but does not have the
  224. // visual importance of a heading.
  225. $form[$collection][$config_change_type]['heading'] = array(
  226. '#type' => 'html_tag',
  227. '#tag' => 'h3',
  228. );
  229. switch ($config_change_type) {
  230. case 'create':
  231. $form[$collection][$config_change_type]['heading']['#value'] = $this->formatPlural(count($config_names), '@count new', '@count new');
  232. break;
  233. case 'update':
  234. $form[$collection][$config_change_type]['heading']['#value'] = $this->formatPlural(count($config_names), '@count changed', '@count changed');
  235. break;
  236. case 'delete':
  237. $form[$collection][$config_change_type]['heading']['#value'] = $this->formatPlural(count($config_names), '@count removed', '@count removed');
  238. break;
  239. case 'rename':
  240. $form[$collection][$config_change_type]['heading']['#value'] = $this->formatPlural(count($config_names), '@count renamed', '@count renamed');
  241. break;
  242. }
  243. $form[$collection][$config_change_type]['list'] = array(
  244. '#type' => 'table',
  245. '#header' => array($this->t('Name'), $this->t('Operations')),
  246. );
  247. foreach ($config_names as $config_name) {
  248. if ($config_change_type == 'rename') {
  249. $names = $storage_comparer->extractRenameNames($config_name);
  250. $route_options = array('source_name' => $names['old_name'], 'target_name' => $names['new_name']);
  251. $config_name = $this->t('@source_name to @target_name', array('@source_name' => $names['old_name'], '@target_name' => $names['new_name']));
  252. }
  253. else {
  254. $route_options = array('source_name' => $config_name);
  255. }
  256. if ($collection != StorageInterface::DEFAULT_COLLECTION) {
  257. $route_name = 'config.diff_collection';
  258. $route_options['collection'] = $collection;
  259. }
  260. else {
  261. $route_name = 'config.diff';
  262. }
  263. $links['view_diff'] = array(
  264. 'title' => $this->t('View differences'),
  265. 'url' => Url::fromRoute($route_name, $route_options),
  266. 'attributes' => array(
  267. 'class' => array('use-ajax'),
  268. 'data-dialog-type' => 'modal',
  269. 'data-dialog-options' => json_encode(array(
  270. 'width' => 700
  271. )),
  272. ),
  273. );
  274. $form[$collection][$config_change_type]['list']['#rows'][] = array(
  275. 'name' => $config_name,
  276. 'operations' => array(
  277. 'data' => array(
  278. '#type' => 'operations',
  279. '#links' => $links,
  280. ),
  281. ),
  282. );
  283. }
  284. }
  285. }
  286. return $form;
  287. }
  288. /**
  289. * {@inheritdoc}
  290. */
  291. public function submitForm(array &$form, FormStateInterface $form_state) {
  292. $config_importer = new ConfigImporter(
  293. $form_state->get('storage_comparer'),
  294. $this->eventDispatcher,
  295. $this->configManager,
  296. $this->lock,
  297. $this->typedConfigManager,
  298. $this->moduleHandler,
  299. $this->moduleInstaller,
  300. $this->themeHandler,
  301. $this->getStringTranslation()
  302. );
  303. if ($config_importer->alreadyImporting()) {
  304. drupal_set_message($this->t('Another request may be synchronizing configuration already.'));
  305. }
  306. else {
  307. try {
  308. $sync_steps = $config_importer->initialize();
  309. $batch = array(
  310. 'operations' => array(),
  311. 'finished' => array(get_class($this), 'finishBatch'),
  312. 'title' => t('Synchronizing configuration'),
  313. 'init_message' => t('Starting configuration synchronization.'),
  314. 'progress_message' => t('Completed step @current of @total.'),
  315. 'error_message' => t('Configuration synchronization has encountered an error.'),
  316. 'file' => __DIR__ . '/../../config.admin.inc',
  317. );
  318. foreach ($sync_steps as $sync_step) {
  319. $batch['operations'][] = array(array(get_class($this), 'processBatch'), array($config_importer, $sync_step));
  320. }
  321. batch_set($batch);
  322. }
  323. catch (ConfigImporterException $e) {
  324. // There are validation errors.
  325. drupal_set_message($this->t('The configuration cannot be imported because it failed validation for the following reasons:'), 'error');
  326. foreach ($config_importer->getErrors() as $message) {
  327. drupal_set_message($message, 'error');
  328. }
  329. }
  330. }
  331. }
  332. /**
  333. * Processes the config import batch and persists the importer.
  334. *
  335. * @param \Drupal\Core\Config\ConfigImporter $config_importer
  336. * The batch config importer object to persist.
  337. * @param string $sync_step
  338. * The synchronization step to do.
  339. * @param array $context
  340. * The batch context.
  341. */
  342. public static function processBatch(ConfigImporter $config_importer, $sync_step, &$context) {
  343. if (!isset($context['sandbox']['config_importer'])) {
  344. $context['sandbox']['config_importer'] = $config_importer;
  345. }
  346. $config_importer = $context['sandbox']['config_importer'];
  347. $config_importer->doSyncStep($sync_step, $context);
  348. if ($errors = $config_importer->getErrors()) {
  349. if (!isset($context['results']['errors'])) {
  350. $context['results']['errors'] = array();
  351. }
  352. $context['results']['errors'] += $errors;
  353. }
  354. }
  355. /**
  356. * Finish batch.
  357. *
  358. * This function is a static function to avoid serializing the ConfigSync
  359. * object unnecessarily.
  360. */
  361. public static function finishBatch($success, $results, $operations) {
  362. if ($success) {
  363. if (!empty($results['errors'])) {
  364. foreach ($results['errors'] as $error) {
  365. drupal_set_message($error, 'error');
  366. \Drupal::logger('config_sync')->error($error);
  367. }
  368. drupal_set_message(\Drupal::translation()->translate('The configuration was imported with errors.'), 'warning');
  369. }
  370. else {
  371. drupal_set_message(\Drupal::translation()->translate('The configuration was imported successfully.'));
  372. }
  373. }
  374. else {
  375. // An error occurred.
  376. // $operations contains the operations that remained unprocessed.
  377. $error_operation = reset($operations);
  378. $message = \Drupal::translation()->translate('An error occurred while processing %error_operation with arguments: @arguments', array('%error_operation' => $error_operation[0], '@arguments' => print_r($error_operation[1], TRUE)));
  379. drupal_set_message($message, 'error');
  380. }
  381. }
  382. }