RemoveOrphanEventsAndContacts.php 3.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-License-Identifier: AGPL-3.0-or-later
  6. */
  7. namespace OCA\DAV\Migration;
  8. use OCA\DAV\CalDAV\CalDavBackend;
  9. use OCP\DB\QueryBuilder\IQueryBuilder;
  10. use OCP\IDBConnection;
  11. use OCP\Migration\IOutput;
  12. use OCP\Migration\IRepairStep;
  13. class RemoveOrphanEventsAndContacts implements IRepairStep {
  14. public function __construct(
  15. private IDBConnection $connection,
  16. ) {
  17. }
  18. /**
  19. * @inheritdoc
  20. */
  21. public function getName(): string {
  22. return 'Clean up orphan event and contact data';
  23. }
  24. /**
  25. * @inheritdoc
  26. */
  27. public function run(IOutput $output) {
  28. $orphanItems = $this->removeOrphanChildren('calendarobjects', 'calendars', 'calendarid');
  29. $output->info(sprintf('%d events without a calendar have been cleaned up', $orphanItems));
  30. $orphanItems = $this->removeOrphanChildren('calendarobjects_props', 'calendarobjects', 'objectid');
  31. $output->info(sprintf('%d properties without an events have been cleaned up', $orphanItems));
  32. $orphanItems = $this->removeOrphanChildren('calendarchanges', 'calendars', 'calendarid');
  33. $output->info(sprintf('%d changes without a calendar have been cleaned up', $orphanItems));
  34. $orphanItems = $this->removeOrphanChildren('calendarobjects', 'calendarsubscriptions', 'calendarid');
  35. $output->info(sprintf('%d cached events without a calendar subscription have been cleaned up', $orphanItems));
  36. $orphanItems = $this->removeOrphanChildren('calendarchanges', 'calendarsubscriptions', 'calendarid');
  37. $output->info(sprintf('%d changes without a calendar subscription have been cleaned up', $orphanItems));
  38. $orphanItems = $this->removeOrphanChildren('cards', 'addressbooks', 'addressbookid');
  39. $output->info(sprintf('%d contacts without an addressbook have been cleaned up', $orphanItems));
  40. $orphanItems = $this->removeOrphanChildren('cards_properties', 'cards', 'cardid');
  41. $output->info(sprintf('%d properties without a contact have been cleaned up', $orphanItems));
  42. $orphanItems = $this->removeOrphanChildren('addressbookchanges', 'addressbooks', 'addressbookid');
  43. $output->info(sprintf('%d changes without an addressbook have been cleaned up', $orphanItems));
  44. }
  45. protected function removeOrphanChildren($childTable, $parentTable, $parentId): int {
  46. $qb = $this->connection->getQueryBuilder();
  47. $qb->select('c.id')
  48. ->from($childTable, 'c')
  49. ->leftJoin('c', $parentTable, 'p', $qb->expr()->eq('c.' . $parentId, 'p.id'))
  50. ->where($qb->expr()->isNull('p.id'));
  51. if (\in_array($parentTable, ['calendars', 'calendarsubscriptions'], true)) {
  52. $calendarType = $parentTable === 'calendarsubscriptions' ? CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION : CalDavBackend::CALENDAR_TYPE_CALENDAR;
  53. $qb->andWhere($qb->expr()->eq('c.calendartype', $qb->createNamedParameter($calendarType, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT));
  54. }
  55. $result = $qb->executeQuery();
  56. $orphanItems = [];
  57. while ($row = $result->fetch()) {
  58. $orphanItems[] = (int)$row['id'];
  59. }
  60. $result->closeCursor();
  61. if (!empty($orphanItems)) {
  62. $qb->delete($childTable)
  63. ->where($qb->expr()->in('id', $qb->createParameter('ids')));
  64. $orphanItemsBatch = array_chunk($orphanItems, 200);
  65. foreach ($orphanItemsBatch as $items) {
  66. $qb->setParameter('ids', $items, IQueryBuilder::PARAM_INT_ARRAY);
  67. $qb->executeStatement();
  68. }
  69. }
  70. return count($orphanItems);
  71. }
  72. }