PageRenderTime 43ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

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

https://gitlab.com/geeta7/drupal
PHP | 423 lines | 255 code | 39 blank | 129 comment | 20 complexity | b5f4559909f0252a33772098e7733f55 MD5 | raw file
  1. <?php
  2. /**
  3. * @file
  4. * Contains \Drupal\config\Form\ConfigSingleImportForm.
  5. */
  6. namespace Drupal\config\Form;
  7. use Drupal\Component\Serialization\Yaml;
  8. use Drupal\config\StorageReplaceDataWrapper;
  9. use Drupal\Core\Config\ConfigImporter;
  10. use Drupal\Core\Config\ConfigImporterException;
  11. use Drupal\Core\Config\ConfigManagerInterface;
  12. use Drupal\Core\Config\StorageComparer;
  13. use Drupal\Core\Config\StorageInterface;
  14. use Drupal\Core\Config\TypedConfigManagerInterface;
  15. use Drupal\Core\Entity\EntityManagerInterface;
  16. use Drupal\Core\Extension\ModuleHandlerInterface;
  17. use Drupal\Core\Extension\ModuleInstallerInterface;
  18. use Drupal\Core\Extension\ThemeHandlerInterface;
  19. use Drupal\Core\Form\ConfirmFormBase;
  20. use Drupal\Core\Form\FormStateInterface;
  21. use Drupal\Core\Lock\LockBackendInterface;
  22. use Drupal\Core\Render\RendererInterface;
  23. use Drupal\Core\Url;
  24. use Symfony\Component\DependencyInjection\ContainerInterface;
  25. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  26. /**
  27. * Provides a form for importing a single configuration file.
  28. */
  29. class ConfigSingleImportForm extends ConfirmFormBase {
  30. /**
  31. * The entity manager.
  32. *
  33. * @var \Drupal\Core\Entity\EntityManagerInterface
  34. */
  35. protected $entityManager;
  36. /**
  37. * The config storage.
  38. *
  39. * @var \Drupal\Core\Config\StorageInterface
  40. */
  41. protected $configStorage;
  42. /**
  43. * The renderer service.
  44. *
  45. * @var \Drupal\Core\Render\RendererInterface
  46. */
  47. protected $renderer;
  48. /**
  49. * The event dispatcher.
  50. *
  51. * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
  52. */
  53. protected $eventDispatcher;
  54. /**
  55. * The configuration manager.
  56. *
  57. * @var \Drupal\Core\Config\ConfigManagerInterface;
  58. */
  59. protected $configManager;
  60. /**
  61. * The database lock object.
  62. *
  63. * @var \Drupal\Core\Lock\LockBackendInterface
  64. */
  65. protected $lock;
  66. /**
  67. * The typed config manager.
  68. *
  69. * @var \Drupal\Core\Config\TypedConfigManagerInterface
  70. */
  71. protected $typedConfigManager;
  72. /**
  73. * The module handler.
  74. *
  75. * @var \Drupal\Core\Extension\ModuleHandlerInterface
  76. */
  77. protected $moduleHandler;
  78. /**
  79. * The theme handler.
  80. *
  81. * @var \Drupal\Core\Extension\ThemeHandlerInterface
  82. */
  83. protected $themeHandler;
  84. /**
  85. * The module installer.
  86. *
  87. * @var \Drupal\Core\Extension\ModuleInstallerInterface
  88. */
  89. protected $moduleInstaller;
  90. /**
  91. * If the config exists, this is that object. Otherwise, FALSE.
  92. *
  93. * @var \Drupal\Core\Config\Config|\Drupal\Core\Config\Entity\ConfigEntityInterface|bool
  94. */
  95. protected $configExists = FALSE;
  96. /**
  97. * The submitted data needing to be confirmed.
  98. *
  99. * @var array
  100. */
  101. protected $data = array();
  102. /**
  103. * Constructs a new ConfigSingleImportForm.
  104. *
  105. * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
  106. * The entity manager.
  107. * @param \Drupal\Core\Config\StorageInterface $config_storage
  108. * The config storage.
  109. * @param \Drupal\Core\Render\RendererInterface $renderer
  110. * The renderer service.
  111. * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
  112. * The event dispatcher used to notify subscribers of config import events.
  113. * @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
  114. * The configuration manager.
  115. * @param \Drupal\Core\Lock\LockBackendInterface $lock
  116. * The lock backend to ensure multiple imports do not occur at the same time.
  117. * @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
  118. * The typed configuration manager.
  119. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
  120. * The module handler.
  121. * @param \Drupal\Core\Extension\ModuleInstallerInterface $module_installer
  122. * The module installer.
  123. * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
  124. * The theme handler.
  125. */
  126. public function __construct(EntityManagerInterface $entity_manager, StorageInterface $config_storage, RendererInterface $renderer, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, LockBackendInterface $lock, TypedConfigManagerInterface $typed_config, ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, ThemeHandlerInterface $theme_handler) {
  127. $this->entityManager = $entity_manager;
  128. $this->configStorage = $config_storage;
  129. $this->renderer = $renderer;
  130. // Services necessary for \Drupal\Core\Config\ConfigImporter.
  131. $this->eventDispatcher = $event_dispatcher;
  132. $this->configManager = $config_manager;
  133. $this->lock = $lock;
  134. $this->typedConfigManager = $typed_config;
  135. $this->moduleHandler = $module_handler;
  136. $this->moduleInstaller = $module_installer;
  137. $this->themeHandler = $theme_handler;
  138. }
  139. /**
  140. * {@inheritdoc}
  141. */
  142. public static function create(ContainerInterface $container) {
  143. return new static(
  144. $container->get('entity.manager'),
  145. $container->get('config.storage'),
  146. $container->get('renderer'),
  147. $container->get('event_dispatcher'),
  148. $container->get('config.manager'),
  149. $container->get('lock.persistent'),
  150. $container->get('config.typed'),
  151. $container->get('module_handler'),
  152. $container->get('module_installer'),
  153. $container->get('theme_handler')
  154. );
  155. }
  156. /**
  157. * {@inheritdoc}
  158. */
  159. public function getFormId() {
  160. return 'config_single_import_form';
  161. }
  162. /**
  163. * {@inheritdoc}
  164. */
  165. public function getCancelUrl() {
  166. return new Url('config.import_single');
  167. }
  168. /**
  169. * {@inheritdoc}
  170. */
  171. public function getQuestion() {
  172. if ($this->data['config_type'] === 'system.simple') {
  173. $name = $this->data['config_name'];
  174. $type = $this->t('simple configuration');
  175. }
  176. else {
  177. $definition = $this->entityManager->getDefinition($this->data['config_type']);
  178. $name = $this->data['import'][$definition->getKey('id')];
  179. $type = $definition->getLowercaseLabel();
  180. }
  181. $args = array(
  182. '%name' => $name,
  183. '@type' => strtolower($type),
  184. );
  185. if ($this->configExists) {
  186. $question = $this->t('Are you sure you want to update the %name @type?', $args);
  187. }
  188. else {
  189. $question = $this->t('Are you sure you want to create a new %name @type?', $args);
  190. }
  191. return $question;
  192. }
  193. /**
  194. * {@inheritdoc}
  195. */
  196. public function buildForm(array $form, FormStateInterface $form_state) {
  197. // When this is the confirmation step fall through to the confirmation form.
  198. if ($this->data) {
  199. return parent::buildForm($form, $form_state);
  200. }
  201. $entity_types = array();
  202. foreach ($this->entityManager->getDefinitions() as $entity_type => $definition) {
  203. if ($definition->isSubclassOf('Drupal\Core\Config\Entity\ConfigEntityInterface')) {
  204. $entity_types[$entity_type] = $definition->getLabel();
  205. }
  206. }
  207. // Sort the entity types by label, then add the simple config to the top.
  208. uasort($entity_types, 'strnatcasecmp');
  209. $config_types = array(
  210. 'system.simple' => $this->t('Simple configuration'),
  211. ) + $entity_types;
  212. $form['config_type'] = array(
  213. '#title' => $this->t('Configuration type'),
  214. '#type' => 'select',
  215. '#options' => $config_types,
  216. '#required' => TRUE,
  217. );
  218. $form['config_name'] = array(
  219. '#title' => $this->t('Configuration name'),
  220. '#description' => $this->t('Enter the name of the configuration file without the <em>.yml</em> extension. (e.g. <em>system.site</em>)'),
  221. '#type' => 'textfield',
  222. '#states' => array(
  223. 'required' => array(
  224. ':input[name="config_type"]' => array('value' => 'system.simple'),
  225. ),
  226. 'visible' => array(
  227. ':input[name="config_type"]' => array('value' => 'system.simple'),
  228. ),
  229. ),
  230. );
  231. $form['import'] = array(
  232. '#title' => $this->t('Paste your configuration here'),
  233. '#type' => 'textarea',
  234. '#rows' => 24,
  235. '#required' => TRUE,
  236. );
  237. $form['advanced'] = array(
  238. '#type' => 'details',
  239. '#title' => $this->t('Advanced'),
  240. );
  241. $form['advanced']['custom_entity_id'] = array(
  242. '#title' => $this->t('Custom Entity ID'),
  243. '#type' => 'textfield',
  244. '#description' => $this->t('Specify a custom entity ID. This will override the entity ID in the configuration above.'),
  245. );
  246. $form['actions'] = array('#type' => 'actions');
  247. $form['actions']['submit'] = array(
  248. '#type' => 'submit',
  249. '#value' => $this->t('Import'),
  250. '#button_type' => 'primary',
  251. );
  252. return $form;
  253. }
  254. /**
  255. * {@inheritdoc}
  256. */
  257. public function validateForm(array &$form, FormStateInterface $form_state) {
  258. // The confirmation step needs no additional validation.
  259. if ($this->data) {
  260. return;
  261. }
  262. // Decode the submitted import.
  263. $data = Yaml::decode($form_state->getValue('import'));
  264. // Validate for config entities.
  265. if ($form_state->getValue('config_type') !== 'system.simple') {
  266. $definition = $this->entityManager->getDefinition($form_state->getValue('config_type'));
  267. $id_key = $definition->getKey('id');
  268. // If a custom entity ID is specified, override the value in the
  269. // configuration data being imported.
  270. if (!$form_state->isValueEmpty('custom_entity_id')) {
  271. $data[$id_key] = $form_state->getValue('custom_entity_id');
  272. }
  273. $entity_storage = $this->entityManager->getStorage($form_state->getValue('config_type'));
  274. // If an entity ID was not specified, set an error.
  275. if (!isset($data[$id_key])) {
  276. $form_state->setErrorByName('import', $this->t('Missing ID key "@id_key" for this @entity_type import.', array('@id_key' => $id_key, '@entity_type' => $definition->getLabel())));
  277. return;
  278. }
  279. $config_name = $definition->getConfigPrefix() . '.' . $data[$id_key];
  280. // If there is an existing entity, ensure matching ID and UUID.
  281. if ($entity = $entity_storage->load($data[$id_key])) {
  282. $this->configExists = $entity;
  283. if (!isset($data['uuid'])) {
  284. $form_state->setErrorByName('import', $this->t('An entity with this machine name already exists but the import did not specify a UUID.'));
  285. return;
  286. }
  287. if ($data['uuid'] !== $entity->uuid()) {
  288. $form_state->setErrorByName('import', $this->t('An entity with this machine name already exists but the UUID does not match.'));
  289. return;
  290. }
  291. }
  292. // If there is no entity with a matching ID, check for a UUID match.
  293. elseif (isset($data['uuid']) && $entity_storage->loadByProperties(array('uuid' => $data['uuid']))) {
  294. $form_state->setErrorByName('import', $this->t('An entity with this UUID already exists but the machine name does not match.'));
  295. }
  296. }
  297. else {
  298. $config_name = $form_state->getValue('config_name');
  299. $config = $this->config($config_name);
  300. $this->configExists = !$config->isNew() ? $config : FALSE;
  301. }
  302. // Use ConfigImporter validation.
  303. if (!$form_state->getErrors()) {
  304. $source_storage = new StorageReplaceDataWrapper($this->configStorage);
  305. $source_storage->replaceData($config_name, $data);
  306. $storage_comparer = new StorageComparer(
  307. $source_storage,
  308. $this->configStorage,
  309. $this->configManager
  310. );
  311. if (!$storage_comparer->createChangelist()->hasChanges()) {
  312. $form_state->setErrorByName('import', $this->t('There are no changes to import.'));
  313. }
  314. else {
  315. $config_importer = new ConfigImporter(
  316. $storage_comparer,
  317. $this->eventDispatcher,
  318. $this->configManager,
  319. $this->lock,
  320. $this->typedConfigManager,
  321. $this->moduleHandler,
  322. $this->moduleInstaller,
  323. $this->themeHandler,
  324. $this->getStringTranslation()
  325. );
  326. try {
  327. $config_importer->validate();
  328. $form_state->set('config_importer', $config_importer);
  329. }
  330. catch (ConfigImporterException $e) {
  331. // There are validation errors.
  332. $item_list = [
  333. '#theme' => 'item_list',
  334. '#items' => $config_importer->getErrors(),
  335. '#title' => $this->t('The configuration cannot be imported because it failed validation for the following reasons:'),
  336. ];
  337. $form_state->setErrorByName('import', $this->renderer->render($item_list));
  338. }
  339. }
  340. }
  341. // Store the decoded version of the submitted import.
  342. $form_state->setValueForElement($form['import'], $data);
  343. }
  344. /**
  345. * {@inheritdoc}
  346. */
  347. public function submitForm(array &$form, FormStateInterface $form_state) {
  348. // If this form has not yet been confirmed, store the values and rebuild.
  349. if (!$this->data) {
  350. $form_state->setRebuild();
  351. $this->data = $form_state->getValues();
  352. return;
  353. }
  354. /** @var \Drupal\Core\Config\ConfigImporter $config_importer */
  355. $config_importer = $form_state->get('config_importer');
  356. if ($config_importer->alreadyImporting()) {
  357. drupal_set_message($this->t('Another request may be importing configuration already.'), 'error');
  358. }
  359. else{
  360. try {
  361. $sync_steps = $config_importer->initialize();
  362. $batch = [
  363. 'operations' => [],
  364. 'finished' => [ConfigSync::class, 'finishBatch'],
  365. 'title' => $this->t('Importing configuration'),
  366. 'init_message' => $this->t('Starting configuration import.'),
  367. 'progress_message' => $this->t('Completed @current step of @total.'),
  368. 'error_message' => $this->t('Configuration import has encountered an error.'),
  369. ];
  370. foreach ($sync_steps as $sync_step) {
  371. $batch['operations'][] = [[ConfigSync::class, 'processBatch'], [$config_importer, $sync_step]];
  372. }
  373. batch_set($batch);
  374. }
  375. catch (ConfigImporterException $e) {
  376. // There are validation errors.
  377. drupal_set_message($this->t('The configuration import failed for the following reasons:'), 'error');
  378. foreach ($config_importer->getErrors() as $message) {
  379. drupal_set_message($message, 'error');
  380. }
  381. }
  382. }
  383. }
  384. }