diff --git a/cypress/e2e/groupfolders.cy.ts b/cypress/e2e/groupfolders.cy.ts index 7f511e9b4..5b5b73c39 100644 --- a/cypress/e2e/groupfolders.cy.ts +++ b/cypress/e2e/groupfolders.cy.ts @@ -201,7 +201,7 @@ describe('Groupfolders ACLs and trashbin behavior', () => { fileOrFolderDoesNotExistInTrashbin('subfolder1') }) - it.skip('Delete, rename parent and restore', () => { + it('Delete, rename parent and restore', () => { // Create a subfolders and a file as manager cy.login(managerUser) cy.mkdir(managerUser, `/${groupFolderName}/subfolder1`) @@ -238,7 +238,7 @@ describe('Groupfolders ACLs and trashbin behavior', () => { fileOrFolderExists('file1.txt') }) - it.skip('Delete, rename parent and check ACL', () => { + it('Delete, rename parent and check ACL', () => { // Create a subfolders and a file as manager cy.login(managerUser) cy.mkdir(managerUser, `/${groupFolderName}/subfolder1`) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 3b1d22f3a..a06bde607 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -43,6 +43,7 @@ use OCA\GroupFolders\Folder\FolderManager; use OCA\GroupFolders\Listeners\CircleDestroyedEventListener; use OCA\GroupFolders\Listeners\LoadAdditionalScriptsListener; +use OCA\GroupFolders\Listeners\NodeRenamedListener; use OCA\GroupFolders\Mount\MountProvider; use OCA\GroupFolders\Trash\TrashBackend; use OCA\GroupFolders\Trash\TrashManager; @@ -55,6 +56,7 @@ use OCP\AppFramework\Bootstrap\IRegistrationContext; use OCP\AppFramework\Utility\ITimeFactory; use OCP\Files\Config\IMountProviderCollection; +use OCP\Files\Events\Node\NodeRenamedEvent; use OCP\Files\Folder; use OCP\Files\IMimeTypeLoader; use OCP\Files\IRootFolder; @@ -90,6 +92,7 @@ public function register(IRegistrationContext $context): void { $context->registerEventListener(LoadAdditionalScriptsEvent::class, LoadAdditionalScriptsListener::class); $context->registerEventListener(BeforeTemplateRenderedEvent::class, LoadAdditionalScriptsListener::class); $context->registerEventListener(CircleDestroyedEvent::class, CircleDestroyedEventListener::class); + $context->registerEventListener(NodeRenamedEvent::class, NodeRenamedListener::class); $context->registerService('GroupAppFolder', function (ContainerInterface $c): Folder { /** @var IRootFolder $rootFolder */ diff --git a/lib/Listeners/NodeRenamedListener.php b/lib/Listeners/NodeRenamedListener.php new file mode 100644 index 000000000..0068cab52 --- /dev/null +++ b/lib/Listeners/NodeRenamedListener.php @@ -0,0 +1,67 @@ + + * + * @author Côme Chilliet + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\GroupFolders\Listeners; + +use OCA\GroupFolders\Mount\GroupFolderStorage; +use OCA\GroupFolders\Trash\TrashManager; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use OCP\Files\Events\Node\NodeRenamedEvent; +use OCP\Files\Folder; +use Psr\Log\LoggerInterface; + +/** + * @template-implements IEventListener + */ +class NodeRenamedListener implements IEventListener { + public function __construct( + private TrashManager $trashManager, + private LoggerInterface $logger, + ) { + } + + public function handle(Event $event): void { + $source = $event->getSource(); + $target = $event->getTarget(); + // Look at the parent because the node itself is not existing anymore + $sourceStorage = $source->getParent()->getStorage(); + $targetStorage = $target->getStorage(); + + if (($target instanceof Folder) && + $sourceStorage->instanceOfStorage(GroupFolderStorage::class) && + $targetStorage->instanceOfStorage(GroupFolderStorage::class)) { + // Get internal path on parent to avoid NotFoundException + $sourcePath = $source->getParent()->getInternalPath(); + if ($sourcePath !== '') { + $sourcePath .= '/'; + } + $sourcePath .= $source->getName(); + $targetPath = $target->getInternalPath(); + $this->trashManager->updateTrashedChildren($sourceStorage->getFolderId(), $targetStorage->getFolderId(), $sourcePath, $targetPath); + } + } +} diff --git a/lib/Trash/TrashManager.php b/lib/Trash/TrashManager.php index a769da298..4664117fd 100644 --- a/lib/Trash/TrashManager.php +++ b/lib/Trash/TrashManager.php @@ -25,9 +25,9 @@ use OCP\IDBConnection; class TrashManager { - private IDBConnection $connection; - - public function __construct(IDBConnection $connection) { + public function __construct( + private IDBConnection $connection, + ) { $this->connection = $connection; } @@ -90,4 +90,30 @@ public function emptyTrashbin(int $folderId): void { ->where($query->expr()->eq('folder_id', $query->createNamedParameter($folderId, IQueryBuilder::PARAM_INT))); $query->executeStatement(); } + + public function updateTrashedChildren(int $fromFolderId, int $toFolderId, string $fromLocation, string $toLocation): void { + // Update deep children + $query = $this->connection->getQueryBuilder(); + $fun = $query->func(); + $sourceLength = mb_strlen($fromLocation); + $newPathFunction = $fun->concat( + $query->createNamedParameter($toLocation), + $fun->substring('original_location', $query->createNamedParameter($sourceLength + 1, IQueryBuilder::PARAM_INT))// +1 for the ending slash + ); + $query->update('group_folders_trash') + ->set('folder_id', $query->createNamedParameter($toFolderId, IQueryBuilder::PARAM_INT)) + ->set('original_location', $newPathFunction) + ->where($query->expr()->eq('folder_id', $query->createNamedParameter($fromFolderId, IQueryBuilder::PARAM_INT))) + ->andWhere($query->expr()->like('original_location', $query->createNamedParameter($this->connection->escapeLikeParameter($fromLocation) . '/%'))); + $query->executeStatement(); + + // Update direct children + $query = $this->connection->getQueryBuilder(); + $query->update('group_folders_trash') + ->set('folder_id', $query->createNamedParameter($toFolderId, IQueryBuilder::PARAM_INT)) + ->set('original_location', $query->createNamedParameter($toLocation)) + ->where($query->expr()->eq('folder_id', $query->createNamedParameter($fromFolderId, IQueryBuilder::PARAM_INT))) + ->andWhere($query->expr()->eq('original_location', $query->createNamedParameter($fromLocation, IQueryBuilder::PARAM_STR))); + $query->executeStatement(); + } }