RemoveOrphanEventsAndContacts.php 3.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  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. /** @var IDBConnection */
  15. private $connection;
  16. public function __construct(IDBConnection $connection) {
  17. $this->connection = $connection;
  18. }
  19. /**
  20. * @inheritdoc
  21. */
  22. public function getName(): string {
  23. return 'Clean up orphan event and contact data';
  24. }
  25. /**
  26. * @inheritdoc
  27. */
  28. public function run(IOutput $output) {
  29. $orphanItems = $this->removeOrphanChildren('calendarobjects', 'calendars', 'calendarid');
  30. $output->info(sprintf('%d events without a calendar have been cleaned up', $orphanItems));
  31. $orphanItems = $this->removeOrphanChildren('calendarobjects_props', 'calendarobjects', 'objectid');
  32. $output->info(sprintf('%d properties without an events have been cleaned up', $orphanItems));
  33. $orphanItems = $this->removeOrphanChildren('calendarchanges', 'calendars', 'calendarid');
  34. $output->info(sprintf('%d changes without a calendar have been cleaned up', $orphanItems));
  35. $orphanItems = $this->removeOrphanChildren('calendarobjects', 'calendarsubscriptions', 'calendarid');
  36. $output->info(sprintf('%d cached events without a calendar subscription have been cleaned up', $orphanItems));
  37. $orphanItems = $this->removeOrphanChildren('calendarchanges', 'calendarsubscriptions', 'calendarid');
  38. $output->info(sprintf('%d changes without a calendar subscription have been cleaned up', $orphanItems));
  39. $orphanItems = $this->removeOrphanChildren('cards', 'addressbooks', 'addressbookid');
  40. $output->info(sprintf('%d contacts without an addressbook have been cleaned up', $orphanItems));
  41. $orphanItems = $this->removeOrphanChildren('cards_properties', 'cards', 'cardid');
  42. $output->info(sprintf('%d properties without a contact have been cleaned up', $orphanItems));
  43. $orphanItems = $this->removeOrphanChildren('addressbookchanges', 'addressbooks', 'addressbookid');
  44. $output->info(sprintf('%d changes without an addressbook have been cleaned up', $orphanItems));
  45. }
  46. protected function removeOrphanChildren($childTable, $parentTable, $parentId): int {
  47. $qb = $this->connection->getQueryBuilder();
  48. $qb->select('c.id')
  49. ->from($childTable, 'c')
  50. ->leftJoin('c', $parentTable, 'p', $qb->expr()->eq('c.' . $parentId, 'p.id'))
  51. ->where($qb->expr()->isNull('p.id'));
  52. if (\in_array($parentTable, ['calendars', 'calendarsubscriptions'], true)) {
  53. $calendarType = $parentTable === 'calendarsubscriptions' ? CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION : CalDavBackend::CALENDAR_TYPE_CALENDAR;
  54. $qb->andWhere($qb->expr()->eq('c.calendartype', $qb->createNamedParameter($calendarType, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT));
  55. }
  56. $result = $qb->execute();
  57. $orphanItems = [];
  58. while ($row = $result->fetch()) {
  59. $orphanItems[] = (int)$row['id'];
  60. }
  61. $result->closeCursor();
  62. if (!empty($orphanItems)) {
  63. $qb->delete($childTable)
  64. ->where($qb->expr()->in('id', $qb->createParameter('ids')));
  65. $orphanItemsBatch = array_chunk($orphanItems, 200);
  66. foreach ($orphanItemsBatch as $items) {
  67. $qb->setParameter('ids', $items, IQueryBuilder::PARAM_INT_ARRAY);
  68. $qb->execute();
  69. }
  70. }
  71. return count($orphanItems);
  72. }
  73. }