PrepareModulesEntityUninstallForm.php 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. <?php
  2. namespace Drupal\system\Form;
  3. use Drupal\Core\Entity\EntityTypeManagerInterface;
  4. use Drupal\Core\Form\ConfirmFormBase;
  5. use Drupal\Core\Form\FormStateInterface;
  6. use Drupal\Core\Url;
  7. use Symfony\Component\DependencyInjection\ContainerInterface;
  8. use Symfony\Component\HttpFoundation\RedirectResponse;
  9. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  10. /**
  11. * Provides a form removing module content entities data before uninstallation.
  12. *
  13. * @internal
  14. */
  15. class PrepareModulesEntityUninstallForm extends ConfirmFormBase {
  16. /**
  17. * The entity type ID of the entities to delete.
  18. *
  19. * @var string
  20. */
  21. protected $entityTypeId;
  22. /**
  23. * The entity type manager.
  24. *
  25. * @var \Drupal\Core\Entity\EntityTypeManagerInterface
  26. */
  27. protected $entityTypeManager;
  28. /**
  29. * Constructs a PrepareModulesEntityUninstallForm object.
  30. *
  31. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
  32. * The entity type manager.
  33. */
  34. public function __construct(EntityTypeManagerInterface $entity_type_manager) {
  35. $this->entityTypeManager = $entity_type_manager;
  36. }
  37. /**
  38. * {@inheritdoc}
  39. */
  40. public static function create(ContainerInterface $container) {
  41. return new static(
  42. $container->get('entity_type.manager')
  43. );
  44. }
  45. /**
  46. * {@inheritdoc}
  47. */
  48. public function getFormId() {
  49. return 'system_prepare_modules_entity_uninstall';
  50. }
  51. /**
  52. * {@inheritdoc}
  53. */
  54. public function getQuestion() {
  55. $entity_type = $this->entityTypeManager->getDefinition($this->entityTypeId);
  56. return $this->t('Are you sure you want to delete all @entity_type_plural?', ['@entity_type_plural' => $entity_type->getPluralLabel()]);
  57. }
  58. /**
  59. * {@inheritdoc}
  60. */
  61. public function getDescription() {
  62. return $this->t('This action cannot be undone.<br />Make a backup of your database if you want to be able to restore these items.');
  63. }
  64. /**
  65. * {@inheritdoc}
  66. */
  67. public function getConfirmText() {
  68. $entity_type = $this->entityTypeManager->getDefinition($this->entityTypeId);
  69. return $this->t('Delete all @entity_type_plural', ['@entity_type_plural' => $entity_type->getPluralLabel()]);
  70. }
  71. /**
  72. * {@inheritdoc}
  73. */
  74. public function getCancelUrl() {
  75. return Url::fromRoute('system.modules_uninstall');
  76. }
  77. /**
  78. * {@inheritdoc}
  79. */
  80. public function buildForm(array $form, FormStateInterface $form_state, $entity_type_id = NULL) {
  81. $this->entityTypeId = $entity_type_id;
  82. if (!$this->entityTypeManager->hasDefinition($this->entityTypeId)) {
  83. throw new NotFoundHttpException();
  84. }
  85. $form = parent::buildForm($form, $form_state);
  86. $storage = $this->entityTypeManager->getStorage($entity_type_id);
  87. $count = $storage->getQuery()->count()->execute();
  88. $form['entity_type_id'] = [
  89. '#type' => 'value',
  90. '#value' => $entity_type_id,
  91. ];
  92. // Display a list of the 10 entity labels, if possible.
  93. $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
  94. if ($count == 0) {
  95. $form['total'] = [
  96. '#markup' => $this->t(
  97. 'There are 0 @entity_type_plural to delete.',
  98. ['@entity_type_plural' => $entity_type->getPluralLabel()]
  99. ),
  100. ];
  101. }
  102. elseif ($entity_type->hasKey('label')) {
  103. $recent_entity_ids = $storage->getQuery()
  104. ->sort($entity_type->getKey('id'), 'DESC')
  105. ->pager(10)
  106. ->execute();
  107. $recent_entities = $storage->loadMultiple($recent_entity_ids);
  108. $labels = [];
  109. foreach ($recent_entities as $entity) {
  110. $labels[] = $entity->label();
  111. }
  112. if ($labels) {
  113. $form['recent_entity_labels'] = [
  114. '#theme' => 'item_list',
  115. '#items' => $labels,
  116. ];
  117. $more_count = $count - count($labels);
  118. $form['total'] = [
  119. '#markup' => $this->formatPlural(
  120. $more_count,
  121. 'And <strong>@count</strong> more @entity_type_singular.',
  122. 'And <strong>@count</strong> more @entity_type_plural.',
  123. [
  124. '@entity_type_singular' => $entity_type->getSingularLabel(),
  125. '@entity_type_plural' => $entity_type->getPluralLabel(),
  126. ]
  127. ),
  128. '#access' => (bool) $more_count,
  129. ];
  130. }
  131. }
  132. else {
  133. $form['total'] = [
  134. '#markup' => $this->formatPlural(
  135. $count,
  136. 'This will delete <strong>@count</strong> @entity_type_singular.',
  137. 'This will delete <strong>@count</strong> @entity_type_plural.',
  138. [
  139. '@entity_type_singular' => $entity_type->getSingularLabel(),
  140. '@entity_type_plural' => $entity_type->getPluralLabel(),
  141. ]
  142. ),
  143. ];
  144. }
  145. $form['description']['#prefix'] = '<p>';
  146. $form['description']['#suffix'] = '</p>';
  147. $form['description']['#weight'] = 5;
  148. // Only show the delete button if there are entities to delete.
  149. $form['actions']['submit']['#access'] = (bool) $count;
  150. return $form;
  151. }
  152. /**
  153. * {@inheritdoc}
  154. */
  155. public function submitForm(array &$form, FormStateInterface $form_state) {
  156. $entity_type_id = $form_state->getValue('entity_type_id');
  157. $entity_type_plural = $this->entityTypeManager->getDefinition($entity_type_id)->getPluralLabel();
  158. $batch = [
  159. 'title' => t('Deleting @entity_type_plural', [
  160. '@entity_type_plural' => $entity_type_plural,
  161. ]),
  162. 'operations' => [
  163. [
  164. [__CLASS__, 'deleteContentEntities'], [$entity_type_id],
  165. ],
  166. ],
  167. 'finished' => [__CLASS__, 'moduleBatchFinished'],
  168. 'progress_message' => '',
  169. ];
  170. batch_set($batch);
  171. }
  172. /**
  173. * Deletes the content entities of the specified entity type.
  174. *
  175. * @param string $entity_type_id
  176. * The entity type ID from which data will be deleted.
  177. * @param array|\ArrayAccess $context
  178. * The batch context array, passed by reference.
  179. *
  180. * @internal
  181. * This batch callback is only meant to be used by this form.
  182. */
  183. public static function deleteContentEntities($entity_type_id, &$context) {
  184. $storage = \Drupal::entityTypeManager()->getStorage($entity_type_id);
  185. // Set the entity type ID in the results array so we can access it in the
  186. // batch finished callback.
  187. $context['results']['entity_type_id'] = $entity_type_id;
  188. if (!isset($context['sandbox']['progress'])) {
  189. $context['sandbox']['progress'] = 0;
  190. $context['sandbox']['max'] = $storage->getQuery()->count()->execute();
  191. }
  192. $entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
  193. $entity_ids = $storage->getQuery()
  194. ->sort($entity_type->getKey('id'), 'ASC')
  195. ->range(0, 10)
  196. ->execute();
  197. if ($entities = $storage->loadMultiple($entity_ids)) {
  198. $storage->delete($entities);
  199. }
  200. // Sometimes deletes cause secondary deletes. For example, deleting a
  201. // taxonomy term can cause its children to be be deleted too.
  202. $context['sandbox']['progress'] = $context['sandbox']['max'] - $storage->getQuery()->count()->execute();
  203. // Inform the batch engine that we are not finished and provide an
  204. // estimation of the completion level we reached.
  205. if (count($entity_ids) > 0 && $context['sandbox']['progress'] != $context['sandbox']['max']) {
  206. $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
  207. $context['message'] = t('Deleting items... Completed @percentage% (@current of @total).', ['@percentage' => round(100 * $context['sandbox']['progress'] / $context['sandbox']['max']), '@current' => $context['sandbox']['progress'], '@total' => $context['sandbox']['max']]);
  208. }
  209. else {
  210. $context['finished'] = 1;
  211. }
  212. }
  213. /**
  214. * Implements callback_batch_finished().
  215. *
  216. * Finishes the module batch, redirect to the uninstall page and output the
  217. * successful data deletion message.
  218. */
  219. public static function moduleBatchFinished($success, $results, $operations) {
  220. $entity_type_plural = \Drupal::entityTypeManager()->getDefinition($results['entity_type_id'])->getPluralLabel();
  221. \Drupal::messenger()->addStatus(t('All @entity_type_plural have been deleted.', ['@entity_type_plural' => $entity_type_plural]));
  222. return new RedirectResponse(Url::fromRoute('system.modules_uninstall')->setAbsolute()->toString());
  223. }
  224. }