mirror of https://github.com/nextcloud/server
Merge pull request #44643 from nextcloud/feat/trashbin-deleted-by
feat(trashbin): Add deleted by properties
This commit is contained in:
commit
4618e28b18
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2024 Christopher Ng <chrng8@gmail.com>
|
||||
*
|
||||
* @author Christopher Ng <chrng8@gmail.com>
|
||||
*
|
||||
* @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\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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -77,5 +77,10 @@ interface ITrashItem extends FileInfo {
|
|||
*/
|
||||
public function getUser(): IUser;
|
||||
|
||||
/**
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getDeletedBy(): ?IUser;
|
||||
|
||||
public function getTitle(): string;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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<string, int|string|bool|float|string[]|int[]>
|
||||
|
|
|
@ -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<string, array<string, array{location: string, deletedBy: string}>>
|
||||
*/
|
||||
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']);
|
||||
|
|
|
@ -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…");
|
||||
|
|
Loading…
Reference in New Issue