diff --git a/apps/files_trashbin/composer/composer/autoload_classmap.php b/apps/files_trashbin/composer/composer/autoload_classmap.php index f1bc38f1492..052063d3550 100644 --- a/apps/files_trashbin/composer/composer/autoload_classmap.php +++ b/apps/files_trashbin/composer/composer/autoload_classmap.php @@ -26,6 +26,7 @@ return array( '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\\Migration\\Version1020Date20240403003535' => $baseDir . '/../lib/Migration/Version1020Date20240403003535.php', 'OCA\\Files_Trashbin\\Sabre\\AbstractTrash' => $baseDir . '/../lib/Sabre/AbstractTrash.php', 'OCA\\Files_Trashbin\\Sabre\\AbstractTrashFile' => $baseDir . '/../lib/Sabre/AbstractTrashFile.php', 'OCA\\Files_Trashbin\\Sabre\\AbstractTrashFolder' => $baseDir . '/../lib/Sabre/AbstractTrashFolder.php', diff --git a/apps/files_trashbin/composer/composer/autoload_static.php b/apps/files_trashbin/composer/composer/autoload_static.php index 4c4b33574af..4f5227b8889 100644 --- a/apps/files_trashbin/composer/composer/autoload_static.php +++ b/apps/files_trashbin/composer/composer/autoload_static.php @@ -41,6 +41,7 @@ class ComposerStaticInitFiles_Trashbin '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\\Migration\\Version1020Date20240403003535' => __DIR__ . '/..' . '/../lib/Migration/Version1020Date20240403003535.php', 'OCA\\Files_Trashbin\\Sabre\\AbstractTrash' => __DIR__ . '/..' . '/../lib/Sabre/AbstractTrash.php', 'OCA\\Files_Trashbin\\Sabre\\AbstractTrashFile' => __DIR__ . '/..' . '/../lib/Sabre/AbstractTrashFile.php', 'OCA\\Files_Trashbin\\Sabre\\AbstractTrashFolder' => __DIR__ . '/..' . '/../lib/Sabre/AbstractTrashFolder.php', diff --git a/apps/files_trashbin/lib/Helper.php b/apps/files_trashbin/lib/Helper.php index 61d8eb9c715..ba28751e99f 100644 --- a/apps/files_trashbin/lib/Helper.php +++ b/apps/files_trashbin/lib/Helper.php @@ -60,7 +60,7 @@ class Helper { $absoluteDir = $view->getAbsolutePath($dir); $internalPath = $mount->getInternalPath($absoluteDir); - $originalLocations = \OCA\Files_Trashbin\Trashbin::getLocations($user); + $extraData = \OCA\Files_Trashbin\Trashbin::getExtraData($user); $dirContent = $storage->getCache()->getFolderContents($mount->getInternalPath($view->getAbsolutePath($dir))); foreach ($dirContent as $entry) { $entryName = $entry->getName(); @@ -76,8 +76,8 @@ class Helper { } $originalPath = ''; $originalName = substr($entryName, 0, -strlen($timestamp) - 2); - if (isset($originalLocations[$originalName][$timestamp])) { - $originalPath = $originalLocations[$originalName][$timestamp]; + if (isset($extraData[$originalName][$timestamp]['location'])) { + $originalPath = $extraData[$originalName][$timestamp]['location']; if (substr($originalPath, -1) === '/') { $originalPath = substr($originalPath, 0, -1); } @@ -101,6 +101,7 @@ class Helper { $i['extraData'] = $originalName; } } + $i['deletedBy'] = $extraData[$originalName][$timestamp]['deletedBy'] ?? null; $result[] = new FileInfo($absoluteDir . '/' . $i['name'], $storage, $internalPath . '/' . $i['name'], $i, $mount); } diff --git a/apps/files_trashbin/lib/Migration/Version1020Date20240403003535.php b/apps/files_trashbin/lib/Migration/Version1020Date20240403003535.php new file mode 100644 index 00000000000..ea7ebefe5b1 --- /dev/null +++ b/apps/files_trashbin/lib/Migration/Version1020Date20240403003535.php @@ -0,0 +1,56 @@ + + * + * @author Christopher Ng + * + * @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\Files_Trashbin\Migration; + +use Closure; +use OCP\DB\ISchemaWrapper; +use OCP\DB\Types; +use OCP\Migration\IOutput; +use OCP\Migration\SimpleMigrationStep; + +class Version1020Date20240403003535 extends SimpleMigrationStep { + + /** + * @param Closure(): ISchemaWrapper $schemaClosure + */ + public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + + if (!$schema->hasTable('files_trash')) { + return null; + } + + $table = $schema->getTable('files_trash'); + $table->addColumn('deleted_by', Types::STRING, [ + 'notnull' => false, + 'length' => 64, + ]); + + return $schema; + } +} diff --git a/apps/files_trashbin/lib/Sabre/AbstractTrash.php b/apps/files_trashbin/lib/Sabre/AbstractTrash.php index e02e4c5b8ba..6e0ff428207 100644 --- a/apps/files_trashbin/lib/Sabre/AbstractTrash.php +++ b/apps/files_trashbin/lib/Sabre/AbstractTrash.php @@ -28,6 +28,7 @@ namespace OCA\Files_Trashbin\Sabre; use OCA\Files_Trashbin\Trash\ITrashItem; use OCA\Files_Trashbin\Trash\ITrashManager; use OCP\Files\FileInfo; +use OCP\IUser; abstract class AbstractTrash implements ITrash { /** @var ITrashItem */ @@ -89,6 +90,10 @@ abstract class AbstractTrash implements ITrash { return $this->data->getTitle(); } + public function getDeletedBy(): ?IUser { + return $this->data->getDeletedBy(); + } + public function delete() { $this->trashManager->removeItem($this->data); } diff --git a/apps/files_trashbin/lib/Sabre/ITrash.php b/apps/files_trashbin/lib/Sabre/ITrash.php index b6b4e70f3b9..c5063df1d11 100644 --- a/apps/files_trashbin/lib/Sabre/ITrash.php +++ b/apps/files_trashbin/lib/Sabre/ITrash.php @@ -27,6 +27,7 @@ declare(strict_types=1); namespace OCA\Files_Trashbin\Sabre; use OCP\Files\FileInfo; +use OCP\IUser; interface ITrash { public function restore(): bool; @@ -39,6 +40,8 @@ interface ITrash { public function getDeletionTime(): int; + public function getDeletedBy(): ?IUser; + public function getSize(): int|float; public function getFileId(): int; diff --git a/apps/files_trashbin/lib/Sabre/TrashbinPlugin.php b/apps/files_trashbin/lib/Sabre/TrashbinPlugin.php index 72b7332d9b1..512a2c072ea 100644 --- a/apps/files_trashbin/lib/Sabre/TrashbinPlugin.php +++ b/apps/files_trashbin/lib/Sabre/TrashbinPlugin.php @@ -41,6 +41,8 @@ class TrashbinPlugin extends ServerPlugin { public const TRASHBIN_ORIGINAL_LOCATION = '{http://nextcloud.org/ns}trashbin-original-location'; public const TRASHBIN_DELETION_TIME = '{http://nextcloud.org/ns}trashbin-deletion-time'; public const TRASHBIN_TITLE = '{http://nextcloud.org/ns}trashbin-title'; + public const TRASHBIN_DELETED_BY_ID = '{http://nextcloud.org/ns}trashbin-deleted-by-id'; + public const TRASHBIN_DELETED_BY_DISPLAY_NAME = '{http://nextcloud.org/ns}trashbin-deleted-by-display-name'; /** @var Server */ private $server; @@ -83,6 +85,14 @@ class TrashbinPlugin extends ServerPlugin { return $node->getDeletionTime(); }); + $propFind->handle(self::TRASHBIN_DELETED_BY_ID, function () use ($node) { + return $node->getDeletedBy()?->getUID(); + }); + + $propFind->handle(self::TRASHBIN_DELETED_BY_DISPLAY_NAME, function () use ($node) { + return $node->getDeletedBy()?->getDisplayName(); + }); + $propFind->handle(FilesPlugin::SIZE_PROPERTYNAME, function () use ($node) { return $node->getSize(); }); diff --git a/apps/files_trashbin/lib/Trash/ITrashItem.php b/apps/files_trashbin/lib/Trash/ITrashItem.php index 0f9c8144a59..bcce5c6876e 100644 --- a/apps/files_trashbin/lib/Trash/ITrashItem.php +++ b/apps/files_trashbin/lib/Trash/ITrashItem.php @@ -77,5 +77,10 @@ interface ITrashItem extends FileInfo { */ public function getUser(): IUser; + /** + * @since 30.0.0 + */ + public function getDeletedBy(): ?IUser; + public function getTitle(): string; } diff --git a/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php b/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php index a608dc08331..fa71d386794 100644 --- a/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php +++ b/apps/files_trashbin/lib/Trash/LegacyTrashBackend.php @@ -33,16 +33,16 @@ use OCP\Files\IRootFolder; use OCP\Files\NotFoundException; use OCP\Files\Storage\IStorage; use OCP\IUser; +use OCP\IUserManager; class LegacyTrashBackend implements ITrashBackend { /** @var array */ private $deletedFiles = []; - /** @var IRootFolder */ - private $rootFolder; - - public function __construct(IRootFolder $rootFolder) { - $this->rootFolder = $rootFolder; + public function __construct( + private IRootFolder $rootFolder, + private IUserManager $userManager, + ) { } /** @@ -59,6 +59,8 @@ class LegacyTrashBackend implements ITrashBackend { if (!$originalLocation) { $originalLocation = $file->getName(); } + /** @psalm-suppress UndefinedInterfaceMethod */ + $deletedBy = $this->userManager->get($file['deletedBy']) ?? $parent?->getDeletedBy(); $trashFilename = Trashbin::getTrashFilename($file->getName(), $file->getMtime()); return new TrashItem( $this, @@ -66,7 +68,8 @@ class LegacyTrashBackend implements ITrashBackend { $file->getMTime(), $parentTrashPath . '/' . ($isRoot ? $trashFilename : $file->getName()), $file, - $user + $user, + $deletedBy, ); }, $items); } diff --git a/apps/files_trashbin/lib/Trash/TrashItem.php b/apps/files_trashbin/lib/Trash/TrashItem.php index 5c9775c6876..79b9b67d278 100644 --- a/apps/files_trashbin/lib/Trash/TrashItem.php +++ b/apps/files_trashbin/lib/Trash/TrashItem.php @@ -27,33 +27,16 @@ use OCP\Files\FileInfo; use OCP\IUser; class TrashItem implements ITrashItem { - /** @var ITrashBackend */ - private $backend; - /** @var string */ - private $orignalLocation; - /** @var int */ - private $deletedTime; - /** @var string */ - private $trashPath; - /** @var FileInfo */ - private $fileInfo; - /** @var IUser */ - private $user; public function __construct( - ITrashBackend $backend, - string $originalLocation, - int $deletedTime, - string $trashPath, - FileInfo $fileInfo, - IUser $user + private ITrashBackend $backend, + private string $originalLocation, + private int $deletedTime, + private string $trashPath, + private FileInfo $fileInfo, + private IUser $user, + private ?IUser $deletedBy, ) { - $this->backend = $backend; - $this->orignalLocation = $originalLocation; - $this->deletedTime = $deletedTime; - $this->trashPath = $trashPath; - $this->fileInfo = $fileInfo; - $this->user = $user; } public function getTrashBackend(): ITrashBackend { @@ -61,7 +44,7 @@ class TrashItem implements ITrashItem { } public function getOriginalLocation(): string { - return $this->orignalLocation; + return $this->originalLocation; } public function getDeletedTime(): int { @@ -192,6 +175,10 @@ class TrashItem implements ITrashItem { return $this->fileInfo->getParentId(); } + public function getDeletedBy(): ?IUser { + return $this->deletedBy; + } + /** * @inheritDoc * @return array diff --git a/apps/files_trashbin/lib/Trashbin.php b/apps/files_trashbin/lib/Trashbin.php index ced40313d62..e5b22f86bd0 100644 --- a/apps/files_trashbin/lib/Trashbin.php +++ b/apps/files_trashbin/lib/Trashbin.php @@ -126,24 +126,23 @@ class Trashbin { } /** - * get original location of files for user + * get original location and deleted by of files for user * * @param string $user - * @return array (filename => array (timestamp => original location)) + * @return array> */ - public static function getLocations($user) { + public static function getExtraData($user) { $query = \OC::$server->getDatabaseConnection()->getQueryBuilder(); - $query->select('id', 'timestamp', 'location') + $query->select('id', 'timestamp', 'location', 'deleted_by') ->from('files_trash') ->where($query->expr()->eq('user', $query->createNamedParameter($user))); $result = $query->executeQuery(); $array = []; while ($row = $result->fetch()) { - if (isset($array[$row['id']])) { - $array[$row['id']][$row['timestamp']] = $row['location']; - } else { - $array[$row['id']] = [$row['timestamp'] => $row['location']]; - } + $array[$row['id']][$row['timestamp']] = [ + 'location' => (string)$row['location'], + 'deletedBy' => (string)$row['deleted_by'], + ]; } $result->closeCursor(); return $array; @@ -228,7 +227,8 @@ class Trashbin { ->setValue('id', $query->createNamedParameter($targetFilename)) ->setValue('timestamp', $query->createNamedParameter($timestamp)) ->setValue('location', $query->createNamedParameter($targetLocation)) - ->setValue('user', $query->createNamedParameter($user)); + ->setValue('user', $query->createNamedParameter($user)) + ->setValue('deleted_by', $query->createNamedParameter($user)); $result = $query->executeStatement(); if (!$result) { \OC::$server->get(LoggerInterface::class)->error('trash bin database couldn\'t be updated for the files owner', ['app' => 'files_trashbin']); @@ -358,7 +358,8 @@ class Trashbin { ->setValue('id', $query->createNamedParameter($filename)) ->setValue('timestamp', $query->createNamedParameter($timestamp)) ->setValue('location', $query->createNamedParameter($location)) - ->setValue('user', $query->createNamedParameter($owner)); + ->setValue('user', $query->createNamedParameter($owner)) + ->setValue('deleted_by', $query->createNamedParameter($user)); $result = $query->executeStatement(); if (!$result) { \OC::$server->get(LoggerInterface::class)->error('trash bin database couldn\'t be updated', ['app' => 'files_trashbin']); diff --git a/apps/files_trashbin/lib/UserMigration/TrashbinMigrator.php b/apps/files_trashbin/lib/UserMigration/TrashbinMigrator.php index 971d2a7d60b..5b3be5aead6 100644 --- a/apps/files_trashbin/lib/UserMigration/TrashbinMigrator.php +++ b/apps/files_trashbin/lib/UserMigration/TrashbinMigrator.php @@ -96,7 +96,15 @@ class TrashbinMigrator implements IMigrator, ISizeEstimationMigrator { } $output->writeln("Exporting trashbin files…"); $exportDestination->copyFolder($trashbinFolder, static::PATH_FILES_FOLDER); - $originalLocations = \OCA\Files_Trashbin\Trashbin::getLocations($uid); + $originalLocations = []; + // TODO Export all extra data and bump migrator to v2 + foreach (\OCA\Files_Trashbin\Trashbin::getExtraData($uid) as $filename => $extraData) { + $locationData = []; + foreach ($extraData as $timestamp => ['location' => $location]) { + $locationData[$timestamp] = $location; + } + $originalLocations[$filename] = $locationData; + } $exportDestination->addFileContents(static::PATH_LOCATIONS_FILE, json_encode($originalLocations)); } catch (NotFoundException $e) { $output->writeln("No trashbin to export…");