RemoveDeletedUsersCalendarSubscriptions.php 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-License-Identifier: AGPL-3.0-or-later
  6. */
  7. namespace OCA\DAV\Migration;
  8. use OCP\DB\Exception;
  9. use OCP\IDBConnection;
  10. use OCP\IUserManager;
  11. use OCP\Migration\IOutput;
  12. use OCP\Migration\IRepairStep;
  13. class RemoveDeletedUsersCalendarSubscriptions implements IRepairStep {
  14. /** @var IDBConnection */
  15. private $connection;
  16. /** @var IUserManager */
  17. private $userManager;
  18. /** @var int */
  19. private $progress = 0;
  20. /** @var int[] */
  21. private $orphanSubscriptionIds = [];
  22. private const SUBSCRIPTIONS_CHUNK_SIZE = 1000;
  23. public function __construct(IDBConnection $connection, IUserManager $userManager) {
  24. $this->connection = $connection;
  25. $this->userManager = $userManager;
  26. }
  27. /**
  28. * @inheritdoc
  29. */
  30. public function getName(): string {
  31. return 'Clean up old calendar subscriptions from deleted users that were not cleaned-up';
  32. }
  33. /**
  34. * @inheritdoc
  35. */
  36. public function run(IOutput $output) {
  37. $nbSubscriptions = $this->countSubscriptions();
  38. $output->startProgress($nbSubscriptions);
  39. while ($this->progress < $nbSubscriptions) {
  40. $this->checkSubscriptions();
  41. $this->progress += self::SUBSCRIPTIONS_CHUNK_SIZE;
  42. $output->advance(min(self::SUBSCRIPTIONS_CHUNK_SIZE, $nbSubscriptions));
  43. }
  44. $output->finishProgress();
  45. $this->deleteOrphanSubscriptions();
  46. $output->info(sprintf('%d calendar subscriptions without an user have been cleaned up', count($this->orphanSubscriptionIds)));
  47. }
  48. /**
  49. * @throws Exception
  50. */
  51. private function countSubscriptions(): int {
  52. $qb = $this->connection->getQueryBuilder();
  53. $query = $qb->select($qb->func()->count('*'))
  54. ->from('calendarsubscriptions');
  55. $result = $query->execute();
  56. $count = $result->fetchOne();
  57. $result->closeCursor();
  58. if ($count !== false) {
  59. $count = (int)$count;
  60. } else {
  61. $count = 0;
  62. }
  63. return $count;
  64. }
  65. /**
  66. * @throws Exception
  67. */
  68. private function checkSubscriptions(): void {
  69. $qb = $this->connection->getQueryBuilder();
  70. $query = $qb->selectDistinct(['id', 'principaluri'])
  71. ->from('calendarsubscriptions')
  72. ->setMaxResults(self::SUBSCRIPTIONS_CHUNK_SIZE)
  73. ->setFirstResult($this->progress);
  74. $result = $query->execute();
  75. while ($row = $result->fetch()) {
  76. $username = $this->getPrincipal($row['principaluri']);
  77. if (!$this->userManager->userExists($username)) {
  78. $this->orphanSubscriptionIds[] = (int) $row['id'];
  79. }
  80. }
  81. $result->closeCursor();
  82. }
  83. /**
  84. * @throws Exception
  85. */
  86. private function deleteOrphanSubscriptions(): void {
  87. foreach ($this->orphanSubscriptionIds as $orphanSubscriptionID) {
  88. $this->deleteOrphanSubscription($orphanSubscriptionID);
  89. }
  90. }
  91. /**
  92. * @throws Exception
  93. */
  94. private function deleteOrphanSubscription(int $orphanSubscriptionID): void {
  95. $qb = $this->connection->getQueryBuilder();
  96. $qb->delete('calendarsubscriptions')
  97. ->where($qb->expr()->eq('id', $qb->createNamedParameter($orphanSubscriptionID)))
  98. ->executeStatement();
  99. }
  100. private function getPrincipal(string $principalUri): string {
  101. $uri = explode('/', $principalUri);
  102. return array_pop($uri);
  103. }
  104. }