diff --git a/appinfo/info.xml b/appinfo/info.xml
index 9051497e77..5d52697025 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -25,7 +25,7 @@ Developed with ❤️ by [LibreCode](https://librecode.coop). Help us transform
* [Donate with GitHub Sponsor: ](https://github.com/sponsors/libresign)
]]>
- 13.0.0-dev.5
+ 13.0.0-dev.6
agpl
LibreCode
diff --git a/lib/Db/File.php b/lib/Db/File.php
index 7f0dcfddf4..feaa9ab80b 100644
--- a/lib/Db/File.php
+++ b/lib/Db/File.php
@@ -17,14 +17,14 @@
* @method void setId(int $id)
* @method int getId()
* @method void setNodeId(?int $nodeId)
- * @method int getNodeId()
- * @method void setSignedNodeId(int $nodeId)
+ * @method ?int getNodeId()
+ * @method void setSignedNodeId(?int $nodeId)
* @method ?int getSignedNodeId()
- * @method void setSignedHash(string $hash)
+ * @method void setSignedHash(?string $hash)
* @method ?string getSignedHash()
- * @method void setUserId(string $userId)
+ * @method void setUserId(?string $userId)
* @method ?string getUserId()
- * @method void setSignRequestId(int $signRequestId)
+ * @method void setSignRequestId(?int $signRequestId)
* @method ?int getSignRequestId()
* @method void setUuid(string $uuid)
* @method string getUuid()
@@ -50,7 +50,7 @@
* @method ?int getParentFileId()
*/
class File extends Entity {
- protected int $nodeId = 0;
+ protected ?int $nodeId = null;
protected string $uuid = '';
protected ?\DateTime $createdAt = null;
protected string $name = '';
diff --git a/lib/Db/FileMapper.php b/lib/Db/FileMapper.php
index 088f413ef0..a02a628cbb 100644
--- a/lib/Db/FileMapper.php
+++ b/lib/Db/FileMapper.php
@@ -220,44 +220,6 @@ public function getFilesOfAccount(string $userId): array {
return $return;
}
- public function getDeletionContext(int $nodeId): array {
- $fullOuterJoin = $this->db->getQueryBuilder();
- $fullOuterJoin->select($fullOuterJoin->expr()->literal(1));
-
- $qb = $this->db->getQueryBuilder();
- $qb
- ->selectAlias('f.id', 'file_id')
- ->selectAlias('sf.id', 'signed_file_id')
- ->selectAlias('ue.id', 'user_element_id')
- ->selectAlias('fe.file_id', 'file_element_file_id')
- ->from($qb->createFunction('(' . $fullOuterJoin->getSQL() . ')'), 'foj')
- ->leftJoin('foj', 'libresign_file', 'f', $qb->expr()->eq('f.node_id', $qb->createNamedParameter($nodeId, IQueryBuilder::PARAM_INT)))
- ->leftJoin('foj', 'libresign_file', 'sf', $qb->expr()->eq('sf.signed_node_id', $qb->createNamedParameter($nodeId, IQueryBuilder::PARAM_INT)))
- ->leftJoin('foj', 'libresign_user_element', 'ue', $qb->expr()->eq('ue.node_id', $qb->createNamedParameter($nodeId, IQueryBuilder::PARAM_INT)))
- ->leftJoin('foj', 'libresign_file_element', 'fe', $qb->expr()->eq('fe.file_id', 'f.id'))
- ->setMaxResults(1);
-
- $row = $qb->executeQuery()->fetchAssociative();
- if (!$row) {
- return ['type' => 'not_libresign_file', 'fileId' => null];
- }
-
- if (!empty($row['signed_file_id'])) {
- return ['type' => 'signed_file', 'fileId' => (int)$row['signed_file_id']];
- }
- if (!empty($row['file_id'])) {
- return ['type' => 'file', 'fileId' => (int)$row['file_id']];
- }
- if (!empty($row['user_element_id'])) {
- return ['type' => 'user_element', 'fileId' => null];
- }
- if (!empty($row['file_element_file_id'])) {
- return ['type' => 'file_element', 'fileId' => (int)$row['file_element_file_id']];
- }
-
- return ['type' => 'not_libresign_file', 'fileId' => null];
- }
-
public function getTextOfStatus(int|FileStatus $status): string {
if (is_int($status)) {
$status = FileStatus::from($status);
diff --git a/lib/Listener/BeforeNodeDeletedListener.php b/lib/Listener/BeforeNodeDeletedListener.php
index 818d5e9d4f..4333faca0a 100644
--- a/lib/Listener/BeforeNodeDeletedListener.php
+++ b/lib/Listener/BeforeNodeDeletedListener.php
@@ -8,15 +8,15 @@
namespace OCA\Libresign\Listener;
-use OCA\Libresign\Db\FileMapper;
+use OCA\Libresign\Enum\FileStatus;
use OCA\Libresign\Helper\ValidateHelper;
-use OCA\Libresign\Service\RequestSignatureService;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Files\Cache\CacheEntryRemovedEvent;
use OCP\Files\Events\Node\BeforeNodeDeletedEvent;
use OCP\Files\File;
+use OCP\Files\Folder;
use OCP\IDBConnection;
/**
@@ -24,8 +24,6 @@
*/
class BeforeNodeDeletedListener implements IEventListener {
public function __construct(
- private FileMapper $fileMapper,
- private RequestSignatureService $requestSignatureService,
private IDBConnection $db,
) {
}
@@ -34,53 +32,177 @@ public function __construct(
public function handle(Event $event): void {
if ($event instanceof BeforeNodeDeletedEvent) {
$node = $event->getNode();
- if (!$node instanceof File) {
+ if (!$node instanceof File && !$node instanceof Folder) {
return;
}
- if (!in_array($node->getMimeType(), ValidateHelper::VALID_MIMETIPE)) {
+ if ($node instanceof File && !in_array($node->getMimeType(), ValidateHelper::VALID_MIMETIPE)) {
return;
}
- $nodeId = $node->getId();
- $this->delete($nodeId);
+
+ $this->deleteAllByNodeId($node->getId());
return;
}
+
if ($event instanceof CacheEntryRemovedEvent) {
- $this->delete($event->getFileId());
+ $this->deleteAllByNodeId($event->getFileId());
}
- return;
}
- private function delete(int $nodeId): void {
- $context = $this->fileMapper->getDeletionContext($nodeId);
- if ($context['type'] === 'not_libresign_file') {
+ private function deleteAllByNodeId(int $nodeId): void {
+ if ($this->handleSignedFileDeleted($nodeId)) {
return;
}
- switch ($context['type']) {
- case 'signed_file':
- if (!isset($context['fileId'])) {
- return;
- }
- $this->requestSignatureService->deleteRequestSignature(['file' => ['fileId' => $context['fileId']]]);
- break;
- case 'file':
- $libresignFile = $this->fileMapper->getByNodeId($nodeId);
- $this->requestSignatureService->deleteRequestSignature(['file' => ['fileId' => $libresignFile->getId()]]);
- $this->fileMapper->delete($libresignFile);
- break;
- case 'user_element':
- $qb = $this->db->getQueryBuilder();
- $qb->delete('libresign_user_element')
- ->where($qb->expr()->eq('node_id', $qb->createNamedParameter($nodeId, IQueryBuilder::PARAM_INT)))
- ->executeStatement();
- break;
- case 'file_element':
- if (!isset($context['fileId'])) {
- return;
+
+ $fullOuterJoin = $this->db->getQueryBuilder();
+ $fullOuterJoin->select($fullOuterJoin->expr()->literal(1));
+
+ $qb = $this->db->getQueryBuilder();
+ $qb
+ ->selectAlias('current.id', 'file_id')
+ ->selectAlias('current.metadata', 'file_metadata')
+ ->selectAlias('current.signed_node_id', 'current_signed_node_id')
+ ->selectAlias('parent.id', 'parent_id')
+ ->selectAlias('children.id', 'child_id')
+ ->selectAlias('ue.id', 'user_element_id')
+ ->selectAlias('fe.file_id', 'file_element_file_id')
+ ->from($qb->createFunction('(' . $fullOuterJoin->getSQL() . ')'), 'foj')
+ ->leftJoin('foj', 'libresign_file', 'current', $qb->expr()->eq('current.node_id', $qb->createNamedParameter($nodeId, IQueryBuilder::PARAM_INT)))
+ ->leftJoin('current', 'libresign_file', 'parent', $qb->expr()->eq('parent.id', 'current.parent_file_id'))
+ ->leftJoin('current', 'libresign_file', 'children', $qb->expr()->eq('children.parent_file_id', 'current.id'))
+ ->leftJoin('foj', 'libresign_user_element', 'ue', $qb->expr()->eq('ue.node_id', $qb->createNamedParameter($nodeId, IQueryBuilder::PARAM_INT)))
+ ->leftJoin('foj', 'libresign_file_element', 'fe', $qb->expr()->eq('fe.file_id', 'current.id'));
+
+ $cursor = $qb->executeQuery();
+
+ $deletedFiles = [];
+ $deletedUserElements = false;
+ $deletedFileElements = [];
+
+ while ($row = $cursor->fetch()) {
+ if (!empty($row['user_element_id']) && !$deletedUserElements) {
+ $deletedUserElements = true;
+ $this->deleteUserElement($nodeId);
+ }
+
+ if (!empty($row['file_element_file_id']) && !isset($deletedFileElements[$row['file_element_file_id']])) {
+ $deletedFileElements[(int)$row['file_element_file_id']] = true;
+ $this->deleteFileElement((int)$row['file_element_file_id']);
+ }
+
+ if (!empty($row['file_id']) && !isset($deletedFiles[$row['file_id']])) {
+ $deletedFiles[(int)$row['file_id']] = true;
+ $this->markOriginalFileAsDeleted((int)$row['file_id'], isset($row['file_metadata']) ? (string)$row['file_metadata'] : null);
+ }
+ }
+ $cursor->closeCursor();
+ }
+
+ private function handleSignedFileDeleted(int $nodeId): bool {
+ $qb = $this->db->getQueryBuilder();
+ $qb->select('id', 'node_id', 'signed_node_id')
+ ->from('libresign_file')
+ ->where($qb->expr()->eq('signed_node_id', $qb->createNamedParameter($nodeId, IQueryBuilder::PARAM_INT)));
+
+ $files = $qb->executeQuery()->fetchAll();
+
+ foreach ($files as $file) {
+ $fileId = (int)$file['id'];
+ $this->deleteSigningData($fileId);
+ $this->detachSignedFile($fileId);
+ }
+
+ return !empty($files);
+ }
+
+ private function markOriginalFileAsDeleted(int $fileId, ?string $metadataJson = null): void {
+ $existingMetadata = [];
+
+ if ($metadataJson !== null && $metadataJson !== '') {
+ try {
+ $decoded = json_decode($metadataJson, true, 512, JSON_THROW_ON_ERROR);
+ if (is_array($decoded)) {
+ $existingMetadata = $decoded;
}
- $qb = $this->db->getQueryBuilder();
- $qb->delete('libresign_file_element')
- ->where($qb->expr()->eq('file_id', $qb->createNamedParameter($context['fileId'], IQueryBuilder::PARAM_INT)))
- ->executeStatement();
+ } catch (\Throwable $e) {
+ }
+ }
+
+ $existingMetadata['original_file_deleted'] = true;
+ $existingMetadata['original_file_deleted_at'] = (new \DateTime('now', new \DateTimeZone('UTC')))->format(\DateTime::ATOM);
+
+ $update = $this->db->getQueryBuilder();
+ $update->update('libresign_file')
+ ->set('node_id', $update->createNamedParameter(null, IQueryBuilder::PARAM_NULL))
+ ->set('metadata', $update->createNamedParameter(json_encode($existingMetadata), IQueryBuilder::PARAM_STR))
+ ->where($update->expr()->eq('id', $update->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)))
+ ->executeStatement();
+ }
+
+ private function detachSignedFile(int $fileId): void {
+ $qb = $this->db->getQueryBuilder();
+ $qb->update('libresign_file')
+ ->set('signed_node_id', $qb->createNamedParameter(null, IQueryBuilder::PARAM_NULL))
+ ->set('status', $qb->createNamedParameter(FileStatus::DRAFT->value, IQueryBuilder::PARAM_INT))
+ ->set('signed_hash', $qb->createNamedParameter(null, IQueryBuilder::PARAM_NULL))
+ ->where($qb->expr()->eq('id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)))
+ ->executeStatement();
+ }
+
+ private function deleteUserElement(int $nodeId): void {
+ $qb = $this->db->getQueryBuilder();
+ $qb->delete('libresign_user_element')
+ ->where($qb->expr()->eq('node_id', $qb->createNamedParameter($nodeId, IQueryBuilder::PARAM_INT)))
+ ->executeStatement();
+ }
+
+ private function deleteFileElement(int $fileId): void {
+ $qb = $this->db->getQueryBuilder();
+ $qb->delete('libresign_file_element')
+ ->where($qb->expr()->eq('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)))
+ ->executeStatement();
+ }
+
+ private function deleteSigningData(int $fileId): void {
+ $this->deleteIdentifyMethods($fileId);
+ $this->deleteSignRequests($fileId);
+ $this->deleteIdDocs($fileId);
+ $this->deleteFileElement($fileId);
+ }
+
+ private function deleteIdentifyMethods(int $fileId): void {
+ $qb = $this->db->getQueryBuilder();
+ $qb->select('id')
+ ->from('libresign_sign_request')
+ ->where($qb->expr()->eq('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
+ $cursor = $qb->executeQuery();
+
+ while ($row = $cursor->fetch()) {
+ $delete = $this->db->getQueryBuilder();
+ $delete->delete('libresign_identify_method')
+ ->where($delete->expr()->eq('sign_request_id', $delete->createNamedParameter($row['id'], IQueryBuilder::PARAM_INT)))
+ ->executeStatement();
}
+ $cursor->closeCursor();
+ }
+
+ private function deleteSignRequests(int $fileId): void {
+ $qb = $this->db->getQueryBuilder();
+ $qb->delete('libresign_sign_request')
+ ->where($qb->expr()->eq('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)))
+ ->executeStatement();
+ }
+
+ private function deleteIdDocs(int $fileId): void {
+ $qb = $this->db->getQueryBuilder();
+ $qb->delete('libresign_id_docs')
+ ->where($qb->expr()->eq('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)))
+ ->executeStatement();
+ }
+
+ private function deleteFile(int $fileId): void {
+ $qb = $this->db->getQueryBuilder();
+ $qb->delete('libresign_file')
+ ->where($qb->expr()->eq('id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)))
+ ->executeStatement();
}
}
diff --git a/lib/Migration/Version17000Date20260106000000.php b/lib/Migration/Version17000Date20260106000000.php
new file mode 100644
index 0000000000..e5314c507a
--- /dev/null
+++ b/lib/Migration/Version17000Date20260106000000.php
@@ -0,0 +1,36 @@
+hasTable('libresign_file')) {
+ $table = $schema->getTable('libresign_file');
+
+ if ($table->hasColumn('node_id')) {
+ $table->modifyColumn('node_id', [
+ 'notnull' => false,
+ ]);
+ $changed = true;
+ }
+ }
+
+ return $changed ? $schema : null;
+ }
+}