aboutsummaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
authorLouis Chemineau <louis@chmn.me>2024-03-12 18:47:14 +0100
committerLouis Chemineau <louis@chmn.me>2024-03-14 10:55:44 +0100
commit2de9880d79adeeab0e0a01ce3992de404a0c274d (patch)
tree1665d95b3fe23d64a2cd111acd64c90cc4e2658e /apps
parentd9e09d5883e6cca4c61c874e1518dc92dd0a1bde (diff)
downloadnextcloud-server-2de9880d79adeeab0e0a01ce3992de404a0c274d.tar.gz
nextcloud-server-2de9880d79adeeab0e0a01ce3992de404a0c274d.zip
fix(files): Do not require files_trashbin in live photo sync listener
Fix https://github.com/nextcloud/server/issues/43299 Signed-off-by: Louis Chemineau <louis@chmn.me>
Diffstat (limited to 'apps')
-rw-r--r--apps/files/composer/composer/autoload_classmap.php1
-rw-r--r--apps/files/composer/composer/autoload_static.php1
-rw-r--r--apps/files/lib/AppInfo/Application.php4
-rw-r--r--apps/files/lib/Listener/SyncLivePhotosListener.php148
-rw-r--r--apps/files/lib/Service/LivePhotosService.php52
-rw-r--r--apps/files_trashbin/composer/composer/autoload_classmap.php1
-rw-r--r--apps/files_trashbin/composer/composer/autoload_static.php1
-rw-r--r--apps/files_trashbin/lib/AppInfo/Application.php4
-rw-r--r--apps/files_trashbin/lib/Listeners/SyncLivePhotosListener.php148
9 files changed, 226 insertions, 134 deletions
diff --git a/apps/files/composer/composer/autoload_classmap.php b/apps/files/composer/composer/autoload_classmap.php
index 49763bf28f8..f7880847ac0 100644
--- a/apps/files/composer/composer/autoload_classmap.php
+++ b/apps/files/composer/composer/autoload_classmap.php
@@ -68,6 +68,7 @@ return array(
'OCA\\Files\\ResponseDefinitions' => $baseDir . '/../lib/ResponseDefinitions.php',
'OCA\\Files\\Search\\FilesSearchProvider' => $baseDir . '/../lib/Search/FilesSearchProvider.php',
'OCA\\Files\\Service\\DirectEditingService' => $baseDir . '/../lib/Service/DirectEditingService.php',
+ 'OCA\\Files\\Service\\LivePhotosService' => $baseDir . '/../lib/Service/LivePhotosService.php',
'OCA\\Files\\Service\\OwnershipTransferService' => $baseDir . '/../lib/Service/OwnershipTransferService.php',
'OCA\\Files\\Service\\TagService' => $baseDir . '/../lib/Service/TagService.php',
'OCA\\Files\\Service\\UserConfig' => $baseDir . '/../lib/Service/UserConfig.php',
diff --git a/apps/files/composer/composer/autoload_static.php b/apps/files/composer/composer/autoload_static.php
index 5a44f49a599..9ba311f2776 100644
--- a/apps/files/composer/composer/autoload_static.php
+++ b/apps/files/composer/composer/autoload_static.php
@@ -83,6 +83,7 @@ class ComposerStaticInitFiles
'OCA\\Files\\ResponseDefinitions' => __DIR__ . '/..' . '/../lib/ResponseDefinitions.php',
'OCA\\Files\\Search\\FilesSearchProvider' => __DIR__ . '/..' . '/../lib/Search/FilesSearchProvider.php',
'OCA\\Files\\Service\\DirectEditingService' => __DIR__ . '/..' . '/../lib/Service/DirectEditingService.php',
+ 'OCA\\Files\\Service\\LivePhotosService' => __DIR__ . '/..' . '/../lib/Service/LivePhotosService.php',
'OCA\\Files\\Service\\OwnershipTransferService' => __DIR__ . '/..' . '/../lib/Service/OwnershipTransferService.php',
'OCA\\Files\\Service\\TagService' => __DIR__ . '/..' . '/../lib/Service/TagService.php',
'OCA\\Files\\Service\\UserConfig' => __DIR__ . '/..' . '/../lib/Service/UserConfig.php',
diff --git a/apps/files/lib/AppInfo/Application.php b/apps/files/lib/AppInfo/Application.php
index c096d8a757a..d4330f759d3 100644
--- a/apps/files/lib/AppInfo/Application.php
+++ b/apps/files/lib/AppInfo/Application.php
@@ -51,7 +51,6 @@ use OCA\Files\Search\FilesSearchProvider;
use OCA\Files\Service\TagService;
use OCA\Files\Service\UserConfig;
use OCA\Files\Service\ViewConfig;
-use OCA\Files_Trashbin\Events\BeforeNodeRestoredEvent;
use OCP\Activity\IManager as IActivityManager;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
@@ -131,11 +130,10 @@ class Application extends App implements IBootstrap {
$context->registerEventListener(RenderReferenceEvent::class, RenderReferenceEventListener::class);
$context->registerEventListener(BeforeNodeRenamedEvent::class, SyncLivePhotosListener::class);
$context->registerEventListener(BeforeNodeDeletedEvent::class, SyncLivePhotosListener::class);
- $context->registerEventListener(BeforeNodeRestoredEvent::class, SyncLivePhotosListener::class);
$context->registerEventListener(CacheEntryRemovedEvent::class, SyncLivePhotosListener::class);
- $context->registerEventListener(LoadSearchPlugins::class, LoadSearchPluginsListener::class);
$context->registerEventListener(BeforeNodeCopiedEvent::class, SyncLivePhotosListener::class);
$context->registerEventListener(NodeCopiedEvent::class, SyncLivePhotosListener::class);
+ $context->registerEventListener(LoadSearchPlugins::class, LoadSearchPluginsListener::class);
$context->registerSearchProvider(FilesSearchProvider::class);
diff --git a/apps/files/lib/Listener/SyncLivePhotosListener.php b/apps/files/lib/Listener/SyncLivePhotosListener.php
index 8f8234428d6..37a0eadc5ac 100644
--- a/apps/files/lib/Listener/SyncLivePhotosListener.php
+++ b/apps/files/lib/Listener/SyncLivePhotosListener.php
@@ -24,9 +24,7 @@ declare(strict_types=1);
namespace OCA\Files\Listener;
-use OCA\Files_Trashbin\Events\BeforeNodeRestoredEvent;
-use OCA\Files_Trashbin\Trash\ITrashItem;
-use OCA\Files_Trashbin\Trash\ITrashManager;
+use OCA\Files\Service\LivePhotosService;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Exceptions\AbortedEventException;
@@ -39,10 +37,7 @@ use OCP\Files\Events\Node\NodeCopiedEvent;
use OCP\Files\Folder;
use OCP\Files\Node;
use OCP\Files\NotFoundException;
-use OCP\Files\NotPermittedException;
-use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException;
use OCP\FilesMetadata\IFilesMetadataManager;
-use OCP\IUserSession;
/**
* @template-implements IEventListener<Event>
@@ -52,37 +47,40 @@ class SyncLivePhotosListener implements IEventListener {
private array $pendingRenames = [];
/** @var Array<int, bool> */
private array $pendingDeletion = [];
- /** @var Array<int, bool> */
- private array $pendingRestores = [];
public function __construct(
private ?Folder $userFolder,
- private ?IUserSession $userSession,
- private ITrashManager $trashManager,
private IFilesMetadataManager $filesMetadataManager,
+ private LivePhotosService $livePhotosService,
) {
}
public function handle(Event $event): void {
- if ($this->userFolder === null || $this->userSession === null) {
+ if ($this->userFolder === null) {
return;
}
- $peerFile = null;
+ $peerFileId = null;
+
if ($event instanceof BeforeNodeRenamedEvent) {
- $peerFile = $this->getLivePhotoPeer($event->getSource()->getId());
- } elseif ($event instanceof BeforeNodeRestoredEvent) {
- $peerFile = $this->getLivePhotoPeer($event->getSource()->getId());
+ $peerFileId = $this->livePhotosService->getLivePhotoPeerId($event->getSource()->getId());
} elseif ($event instanceof BeforeNodeDeletedEvent) {
- $peerFile = $this->getLivePhotoPeer($event->getNode()->getId());
+ $peerFileId = $this->livePhotosService->getLivePhotoPeerId($event->getNode()->getId());
} elseif ($event instanceof CacheEntryRemovedEvent) {
- $peerFile = $this->getLivePhotoPeer($event->getFileId());
+ $peerFileId = $this->livePhotosService->getLivePhotoPeerId($event->getFileId());
} elseif ($event instanceof BeforeNodeCopiedEvent || $event instanceof NodeCopiedEvent) {
- $peerFile = $this->getLivePhotoPeer($event->getSource()->getId());
+ $peerFileId = $this->livePhotosService->getLivePhotoPeerId($event->getSource()->getId());
+ }
+
+ if ($peerFileId === null) {
+ return; // Not a live photo.
}
+ // Check the user's folder.
+ $peerFile = $this->userFolder->getFirstNodeById($peerFileId);
+
if ($peerFile === null) {
- return; // not a Live Photo
+ return; // Peer file not found.
}
if ($event instanceof BeforeNodeRenamedEvent) {
@@ -91,8 +89,6 @@ class SyncLivePhotosListener implements IEventListener {
$this->handleDeletion($event, $peerFile);
} elseif ($event instanceof CacheEntryRemovedEvent) {
$peerFile->delete();
- } elseif ($event instanceof BeforeNodeRestoredEvent) {
- $this->handleRestore($event, $peerFile);
} elseif ($event instanceof BeforeNodeCopiedEvent) {
$this->handleMove($event, $peerFile, true);
} elseif ($event instanceof NodeCopiedEvent) {
@@ -208,114 +204,4 @@ class SyncLivePhotosListener implements IEventListener {
}
return;
}
-
- /**
- * During restore event, we trigger another recursive restore on the peer file.
- * Restore operations on the .mov file directly are currently blocked.
- * The event listener being singleton, we can store the current state
- * of pending restores inside the 'pendingRestores' property,
- * to prevent infinite recursivity.
- */
- private function handleRestore(BeforeNodeRestoredEvent $event, Node $peerFile): void {
- $sourceFile = $event->getSource();
-
- if ($sourceFile->getMimetype() === 'video/quicktime') {
- if (isset($this->pendingRestores[$peerFile->getId()])) {
- unset($this->pendingRestores[$peerFile->getId()]);
- return;
- } else {
- $event->abortOperation(new NotPermittedException("Cannot restore the video part of a live photo"));
- }
- } else {
- $user = $this->userSession->getUser();
- if ($user === null) {
- return;
- }
-
- $peerTrashItem = $this->trashManager->getTrashNodeById($user, $peerFile->getId());
- // Peer file is not in the bin, no need to restore it.
- if ($peerTrashItem === null) {
- return;
- }
-
- $trashRoot = $this->trashManager->listTrashRoot($user);
- $trashItem = $this->getTrashItem($trashRoot, $peerFile->getInternalPath());
-
- if ($trashItem === null) {
- $event->abortOperation(new NotFoundException("Couldn't find peer file in trashbin"));
- }
-
- $this->pendingRestores[$sourceFile->getId()] = true;
- try {
- $this->trashManager->restoreItem($trashItem);
- } catch (\Throwable $ex) {
- $event->abortOperation($ex);
- }
- }
- }
-
- /**
- * Helper method to get the associated live photo file.
- * We first look for it in the user folder, and if we
- * cannot find it here, we look for it in the user's trashbin.
- */
- private function getLivePhotoPeer(int $nodeId): ?Node {
- if ($this->userFolder === null || $this->userSession === null) {
- return null;
- }
-
- try {
- $metadata = $this->filesMetadataManager->getMetadata($nodeId);
- } catch (FilesMetadataNotFoundException $ex) {
- return null;
- }
-
- if (!$metadata->hasKey('files-live-photo')) {
- return null;
- }
-
- $peerFileId = (int)$metadata->getString('files-live-photo');
-
- // Check the user's folder.
- $node = $this->userFolder->getFirstNodeById($peerFileId);
- if ($node) {
- return $node;
- }
-
- // Check the user's trashbin.
- $user = $this->userSession->getUser();
- if ($user !== null) {
- $peerFile = $this->trashManager->getTrashNodeById($user, $peerFileId);
- if ($peerFile !== null) {
- return $peerFile;
- }
- }
-
- $metadata->unset('files-live-photo');
- return null;
- }
-
- /**
- * There is currently no method to restore a file based on its fileId or path.
- * So we have to manually find a ITrashItem from the trash item list.
- * TODO: This should be replaced by a proper method in the TrashManager.
- */
- private function getTrashItem(array $trashFolder, string $path): ?ITrashItem {
- foreach($trashFolder as $trashItem) {
- if (str_starts_with($path, "files_trashbin/files".$trashItem->getTrashPath())) {
- if ($path === "files_trashbin/files".$trashItem->getTrashPath()) {
- return $trashItem;
- }
-
- if ($trashItem instanceof Folder) {
- $node = $this->getTrashItem($trashItem->getDirectoryListing(), $path);
- if ($node !== null) {
- return $node;
- }
- }
- }
- }
-
- return null;
- }
}
diff --git a/apps/files/lib/Service/LivePhotosService.php b/apps/files/lib/Service/LivePhotosService.php
new file mode 100644
index 00000000000..67738104d15
--- /dev/null
+++ b/apps/files/lib/Service/LivePhotosService.php
@@ -0,0 +1,52 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2024 Louis Chemineau <louis@chmn.me>
+ *
+ * @author Louis Chemineau <louis@chmn.me>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\Files\Service;
+
+use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException;
+use OCP\FilesMetadata\IFilesMetadataManager;
+
+class LivePhotosService {
+ public function __construct(
+ private IFilesMetadataManager $filesMetadataManager,
+ ) {
+ }
+
+ /**
+ * Get the associated live photo for a given file id
+ */
+ public function getLivePhotoPeerId(int $fileId): ?int {
+ try {
+ $metadata = $this->filesMetadataManager->getMetadata($fileId);
+ } catch (FilesMetadataNotFoundException $ex) {
+ return null;
+ }
+
+ if (!$metadata->hasKey('files-live-photo')) {
+ return null;
+ }
+
+ return (int)$metadata->getString('files-live-photo');
+ }
+}
diff --git a/apps/files_trashbin/composer/composer/autoload_classmap.php b/apps/files_trashbin/composer/composer/autoload_classmap.php
index c243d1d291c..f1bc38f1492 100644
--- a/apps/files_trashbin/composer/composer/autoload_classmap.php
+++ b/apps/files_trashbin/composer/composer/autoload_classmap.php
@@ -24,6 +24,7 @@ return array(
'OCA\\Files_Trashbin\\Helper' => $baseDir . '/../lib/Helper.php',
'OCA\\Files_Trashbin\\Hooks' => $baseDir . '/../lib/Hooks.php',
'OCA\\Files_Trashbin\\Listeners\\LoadAdditionalScripts' => $baseDir . '/../lib/Listeners/LoadAdditionalScripts.php',
+ 'OCA\\Files_Trashbin\\Listeners\\SyncLivePhotosListener' => $baseDir . '/../lib/Listeners/SyncLivePhotosListener.php',
'OCA\\Files_Trashbin\\Migration\\Version1010Date20200630192639' => $baseDir . '/../lib/Migration/Version1010Date20200630192639.php',
'OCA\\Files_Trashbin\\Sabre\\AbstractTrash' => $baseDir . '/../lib/Sabre/AbstractTrash.php',
'OCA\\Files_Trashbin\\Sabre\\AbstractTrashFile' => $baseDir . '/../lib/Sabre/AbstractTrashFile.php',
diff --git a/apps/files_trashbin/composer/composer/autoload_static.php b/apps/files_trashbin/composer/composer/autoload_static.php
index ffa3916460b..4c4b33574af 100644
--- a/apps/files_trashbin/composer/composer/autoload_static.php
+++ b/apps/files_trashbin/composer/composer/autoload_static.php
@@ -39,6 +39,7 @@ class ComposerStaticInitFiles_Trashbin
'OCA\\Files_Trashbin\\Helper' => __DIR__ . '/..' . '/../lib/Helper.php',
'OCA\\Files_Trashbin\\Hooks' => __DIR__ . '/..' . '/../lib/Hooks.php',
'OCA\\Files_Trashbin\\Listeners\\LoadAdditionalScripts' => __DIR__ . '/..' . '/../lib/Listeners/LoadAdditionalScripts.php',
+ 'OCA\\Files_Trashbin\\Listeners\\SyncLivePhotosListener' => __DIR__ . '/..' . '/../lib/Listeners/SyncLivePhotosListener.php',
'OCA\\Files_Trashbin\\Migration\\Version1010Date20200630192639' => __DIR__ . '/..' . '/../lib/Migration/Version1010Date20200630192639.php',
'OCA\\Files_Trashbin\\Sabre\\AbstractTrash' => __DIR__ . '/..' . '/../lib/Sabre/AbstractTrash.php',
'OCA\\Files_Trashbin\\Sabre\\AbstractTrashFile' => __DIR__ . '/..' . '/../lib/Sabre/AbstractTrashFile.php',
diff --git a/apps/files_trashbin/lib/AppInfo/Application.php b/apps/files_trashbin/lib/AppInfo/Application.php
index 0f36fe37d29..e12a6ca8af9 100644
--- a/apps/files_trashbin/lib/AppInfo/Application.php
+++ b/apps/files_trashbin/lib/AppInfo/Application.php
@@ -28,8 +28,10 @@ namespace OCA\Files_Trashbin\AppInfo;
use OCA\DAV\Connector\Sabre\Principal;
use OCA\Files\Event\LoadAdditionalScriptsEvent;
use OCA\Files_Trashbin\Capabilities;
+use OCA\Files_Trashbin\Events\BeforeNodeRestoredEvent;
use OCA\Files_Trashbin\Expiration;
use OCA\Files_Trashbin\Listeners\LoadAdditionalScripts;
+use OCA\Files_Trashbin\Listeners\SyncLivePhotosListener;
use OCA\Files_Trashbin\Trash\ITrashManager;
use OCA\Files_Trashbin\Trash\TrashManager;
use OCA\Files_Trashbin\UserMigration\TrashbinMigrator;
@@ -62,6 +64,8 @@ class Application extends App implements IBootstrap {
LoadAdditionalScriptsEvent::class,
LoadAdditionalScripts::class
);
+
+ $context->registerEventListener(BeforeNodeRestoredEvent::class, SyncLivePhotosListener::class);
}
public function boot(IBootContext $context): void {
diff --git a/apps/files_trashbin/lib/Listeners/SyncLivePhotosListener.php b/apps/files_trashbin/lib/Listeners/SyncLivePhotosListener.php
new file mode 100644
index 00000000000..8226f7e40a6
--- /dev/null
+++ b/apps/files_trashbin/lib/Listeners/SyncLivePhotosListener.php
@@ -0,0 +1,148 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2023 Louis Chemineau <louis@chmn.me>
+ *
+ * @author Louis Chemineau <louis@chmn.me>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\Files_Trashbin\Listeners;
+
+use OCA\Files\Service\LivePhotosService;
+use OCA\Files_Trashbin\Events\BeforeNodeRestoredEvent;
+use OCA\Files_Trashbin\Trash\ITrashItem;
+use OCA\Files_Trashbin\Trash\ITrashManager;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+use OCP\Files\Folder;
+use OCP\Files\Node;
+use OCP\Files\NotFoundException;
+use OCP\Files\NotPermittedException;
+use OCP\IUserSession;
+
+/**
+ * @template-implements IEventListener<BeforeNodeRestoredEvent>
+ */
+class SyncLivePhotosListener implements IEventListener {
+ /** @var Array<int, bool> */
+ private array $pendingRestores = [];
+
+ public function __construct(
+ private ?IUserSession $userSession,
+ private ITrashManager $trashManager,
+ private LivePhotosService $livePhotosService,
+ ) {
+ }
+
+ public function handle(Event $event): void {
+ if ($this->userSession === null) {
+ return;
+ }
+
+ /** @var BeforeNodeRestoredEvent $event */
+ $peerFileId = $this->livePhotosService->getLivePhotoPeerId($event->getSource()->getId());
+
+ if ($peerFileId === null) {
+ return; // Not a live photo.
+ }
+
+ // Check the user's trashbin.
+ $user = $this->userSession->getUser();
+ if ($user === null) {
+ return;
+ }
+
+ $peerFile = $this->trashManager->getTrashNodeById($user, $peerFileId);
+
+ if ($peerFile === null) {
+ return; // Peer file not found.
+ }
+
+ $this->handleRestore($event, $peerFile);
+ }
+
+ /**
+ * During restore event, we trigger another recursive restore on the peer file.
+ * Restore operations on the .mov file directly are currently blocked.
+ * The event listener being singleton, we can store the current state
+ * of pending restores inside the 'pendingRestores' property,
+ * to prevent infinite recursivity.
+ */
+ private function handleRestore(BeforeNodeRestoredEvent $event, Node $peerFile): void {
+ $sourceFile = $event->getSource();
+
+ if ($sourceFile->getMimetype() === 'video/quicktime') {
+ if (isset($this->pendingRestores[$peerFile->getId()])) {
+ unset($this->pendingRestores[$peerFile->getId()]);
+ return;
+ } else {
+ $event->abortOperation(new NotPermittedException("Cannot restore the video part of a live photo"));
+ }
+ } else {
+ $user = $this->userSession?->getUser();
+ if ($user === null) {
+ return;
+ }
+
+ $peerTrashItem = $this->trashManager->getTrashNodeById($user, $peerFile->getId());
+ // Peer file is not in the bin, no need to restore it.
+ if ($peerTrashItem === null) {
+ return;
+ }
+
+ $trashRoot = $this->trashManager->listTrashRoot($user);
+ $trashItem = $this->getTrashItem($trashRoot, $peerFile->getInternalPath());
+
+ if ($trashItem === null) {
+ $event->abortOperation(new NotFoundException("Couldn't find peer file in trashbin"));
+ }
+
+ $this->pendingRestores[$sourceFile->getId()] = true;
+ try {
+ $this->trashManager->restoreItem($trashItem);
+ } catch (\Throwable $ex) {
+ $event->abortOperation($ex);
+ }
+ }
+ }
+
+ /**
+ * There is currently no method to restore a file based on its fileId or path.
+ * So we have to manually find a ITrashItem from the trash item list.
+ * TODO: This should be replaced by a proper method in the TrashManager.
+ */
+ private function getTrashItem(array $trashFolder, string $path): ?ITrashItem {
+ foreach($trashFolder as $trashItem) {
+ if (str_starts_with($path, "files_trashbin/files".$trashItem->getTrashPath())) {
+ if ($path === "files_trashbin/files".$trashItem->getTrashPath()) {
+ return $trashItem;
+ }
+
+ if ($trashItem instanceof Folder) {
+ $node = $this->getTrashItem($trashItem->getDirectoryListing(), $path);
+ if ($node !== null) {
+ return $node;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+}