diff --git a/lib/repair/repairinvalidshares.php b/lib/repair/repairinvalidshares.php index 4b0aeb70c1..5a4cb445ce 100644 --- a/lib/repair/repairinvalidshares.php +++ b/lib/repair/repairinvalidshares.php @@ -70,11 +70,43 @@ class RepairInvalidShares extends BasicEmitter implements \OC\RepairStep { } } + /** + * Remove shares where the parent share does not exist anymore + */ + private function removeSharesNonExistingParent() { + $deletedEntries = 0; + + $query = $this->connection->getQueryBuilder(); + $query->select('s1.parent') + ->from('share', 's1') + ->where($query->expr()->isNotNull('s1.parent')) + ->andWhere($query->expr()->isNull('s2.id')) + ->leftJoin('s1', 'share', 's2', $query->expr()->eq('s1.parent', 's2.id')) + ->groupBy('s1.parent'); + + $deleteQuery = $this->connection->getQueryBuilder(); + $deleteQuery->delete('share') + ->where($query->expr()->eq('parent', $deleteQuery->createParameter('parent'))); + + $result = $query->execute(); + while ($row = $result->fetch()) { + $deletedEntries += $deleteQuery->setParameter('parent', (int) $row['parent']) + ->execute(); + } + $result->closeCursor(); + + if ($deletedEntries) { + $this->emit('\OC\Repair', 'info', array('Removed ' . $deletedEntries . ' shares where the parent did not exist')); + } + } + public function run() { $ocVersionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0'); if (version_compare($ocVersionFromBeforeUpdate, '8.2.0.7', '<')) { // this situation was only possible before 8.2 $this->removeExpirationDateFromNonLinkShares(); } + + $this->removeSharesNonExistingParent(); } } diff --git a/tests/lib/repair/repairinvalidsharestest.php b/tests/lib/repair/repairinvalidsharestest.php index 89a5ba470e..7aaf273e3c 100644 --- a/tests/lib/repair/repairinvalidsharestest.php +++ b/tests/lib/repair/repairinvalidsharestest.php @@ -119,5 +119,83 @@ class RepairInvalidSharesTest extends TestCase { $this->assertNull($userShare['expiration'], 'bogus expiration date was removed'); $this->assertNotNull($linkShare['expiration'], 'valid link share expiration date still there'); } + + /** + * Test remove shares where the parent share does not exist anymore + */ + public function testSharesNonExistingParent() { + $qb = $this->connection->getQueryBuilder(); + $shareValues = [ + 'share_type' => $qb->expr()->literal(Constants::SHARE_TYPE_USER), + 'share_with' => $qb->expr()->literal('recipientuser1'), + 'uid_owner' => $qb->expr()->literal('user1'), + 'item_type' => $qb->expr()->literal('folder'), + 'item_source' => $qb->expr()->literal(123), + 'item_target' => $qb->expr()->literal('/123'), + 'file_source' => $qb->expr()->literal(123), + 'file_target' => $qb->expr()->literal('/test'), + 'permissions' => $qb->expr()->literal(1), + 'stime' => $qb->expr()->literal(time()), + 'expiration' => $qb->expr()->literal('2015-09-25 00:00:00') + ]; + + // valid share + $qb = $this->connection->getQueryBuilder(); + $qb->insert('share') + ->values($shareValues) + ->execute(); + $parent = $this->getLastShareId(); + + // share with existing parent + $qb = $this->connection->getQueryBuilder(); + $qb->insert('share') + ->values(array_merge($shareValues, [ + 'parent' => $qb->expr()->literal($parent), + ]))->execute(); + $validChild = $this->getLastShareId(); + + // share with non-existing parent + $qb = $this->connection->getQueryBuilder(); + $qb->insert('share') + ->values(array_merge($shareValues, [ + 'parent' => $qb->expr()->literal($parent + 100), + ]))->execute(); + $invalidChild = $this->getLastShareId(); + + $query = $this->connection->getQueryBuilder(); + $result = $query->select('id') + ->from('share') + ->orderBy('id', 'ASC') + ->execute(); + $rows = $result->fetchAll(); + $this->assertSame([['id' => $parent], ['id' => $validChild], ['id' => $invalidChild]], $rows); + $result->closeCursor(); + + $this->repair->run(); + + $query = $this->connection->getQueryBuilder(); + $result = $query->select('id') + ->from('share') + ->orderBy('id', 'ASC') + ->execute(); + $rows = $result->fetchAll(); + $this->assertSame([['id' => $parent], ['id' => $validChild]], $rows); + $result->closeCursor(); + } + + /** + * @return int + */ + protected function getLastShareId() { + // select because lastInsertId does not work with OCI + $query = $this->connection->getQueryBuilder(); + $result = $query->select('id') + ->from('share') + ->orderBy('id', 'DESC') + ->execute(); + $row = $result->fetch(); + $result->closeCursor(); + return $row['id']; + } }