RemoveDeletedUsersCalendarSubscriptions.php 3.0 KB

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